Spaces:
Running
Running
Upload 372 files
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- index.html +740 -18
- libs/gradio-client.js/1.10.0/gradio-client.js +8 -0
- libs/three.js/0.172.0/jsm/Addons.js +287 -0
- libs/three.js/0.172.0/jsm/animation/AnimationClipCreator.js +116 -0
- libs/three.js/0.172.0/jsm/animation/CCDIKSolver.js +485 -0
- libs/three.js/0.172.0/jsm/capabilities/WebGL.js +114 -0
- libs/three.js/0.172.0/jsm/capabilities/WebGPU.js +48 -0
- libs/three.js/0.172.0/jsm/controls/ArcballControls.js +3253 -0
- libs/three.js/0.172.0/jsm/controls/DragControls.js +410 -0
- libs/three.js/0.172.0/jsm/controls/FirstPersonControls.js +337 -0
- libs/three.js/0.172.0/jsm/controls/FlyControls.js +332 -0
- libs/three.js/0.172.0/jsm/controls/MapControls.js +28 -0
- libs/three.js/0.172.0/jsm/controls/OrbitControls.js +1556 -0
- libs/three.js/0.172.0/jsm/controls/PointerLockControls.js +168 -0
- libs/three.js/0.172.0/jsm/controls/TrackballControls.js +849 -0
- libs/three.js/0.172.0/jsm/controls/TransformControls.js +1624 -0
- libs/three.js/0.172.0/jsm/csm/CSM.js +384 -0
- libs/three.js/0.172.0/jsm/csm/CSMFrustum.js +155 -0
- libs/three.js/0.172.0/jsm/csm/CSMHelper.js +195 -0
- libs/three.js/0.172.0/jsm/csm/CSMShader.js +295 -0
- libs/three.js/0.172.0/jsm/csm/CSMShadowNode.js +442 -0
- libs/three.js/0.172.0/jsm/curves/CurveExtras.js +422 -0
- libs/three.js/0.172.0/jsm/curves/NURBSCurve.js +111 -0
- libs/three.js/0.172.0/jsm/curves/NURBSSurface.js +52 -0
- libs/three.js/0.172.0/jsm/curves/NURBSUtils.js +545 -0
- libs/three.js/0.172.0/jsm/curves/NURBSVolume.js +62 -0
- libs/three.js/0.172.0/jsm/effects/AnaglyphEffect.js +147 -0
- libs/three.js/0.172.0/jsm/effects/AsciiEffect.js +263 -0
- libs/three.js/0.172.0/jsm/effects/OutlineEffect.js +539 -0
- libs/three.js/0.172.0/jsm/effects/ParallaxBarrierEffect.js +125 -0
- libs/three.js/0.172.0/jsm/effects/PeppersGhostEffect.js +153 -0
- libs/three.js/0.172.0/jsm/effects/StereoEffect.js +60 -0
- libs/three.js/0.172.0/jsm/environments/DebugEnvironment.js +52 -0
- libs/three.js/0.172.0/jsm/environments/RoomEnvironment.js +144 -0
- libs/three.js/0.172.0/jsm/exporters/DRACOExporter.js +269 -0
- libs/three.js/0.172.0/jsm/exporters/EXRExporter.js +587 -0
- libs/three.js/0.172.0/jsm/exporters/GLTFExporter.js +3460 -0
- libs/three.js/0.172.0/jsm/exporters/KTX2Exporter.js +323 -0
- libs/three.js/0.172.0/jsm/exporters/OBJExporter.js +288 -0
- libs/three.js/0.172.0/jsm/exporters/PLYExporter.js +530 -0
- libs/three.js/0.172.0/jsm/exporters/STLExporter.js +199 -0
- libs/three.js/0.172.0/jsm/exporters/USDZExporter.js +782 -0
- libs/three.js/0.172.0/jsm/geometries/BoxLineGeometry.js +69 -0
- libs/three.js/0.172.0/jsm/geometries/ConvexGeometry.js +53 -0
- libs/three.js/0.172.0/jsm/geometries/DecalGeometry.js +408 -0
- libs/three.js/0.172.0/jsm/geometries/ParametricGeometries.js +254 -0
- libs/three.js/0.172.0/jsm/geometries/ParametricGeometry.js +139 -0
- libs/three.js/0.172.0/jsm/geometries/RoundedBoxGeometry.js +155 -0
- libs/three.js/0.172.0/jsm/geometries/TeapotGeometry.js +704 -0
- libs/three.js/0.172.0/jsm/geometries/TextGeometry.js +54 -0
index.html
CHANGED
@@ -1,19 +1,741 @@
|
|
1 |
-
<!
|
2 |
<html>
|
3 |
-
|
4 |
-
|
5 |
-
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
<!DOCTYPE html>
|
2 |
<html>
|
3 |
+
<head>
|
4 |
+
<title>Text to Terrain Generator</title>
|
5 |
+
<style>
|
6 |
+
body { margin: 0; }
|
7 |
+
canvas { display: block; }
|
8 |
+
#prompt-input {
|
9 |
+
position: absolute;
|
10 |
+
top: 10px;
|
11 |
+
left: 10px;
|
12 |
+
padding: 8px;
|
13 |
+
width: 200px;
|
14 |
+
border: 1px solid #ccc;
|
15 |
+
border-radius: 4px;
|
16 |
+
font-size: 14px;
|
17 |
+
}
|
18 |
+
#camera-toggle {
|
19 |
+
position: absolute;
|
20 |
+
top: 10px;
|
21 |
+
right: 10px;
|
22 |
+
padding: 8px 16px;
|
23 |
+
background-color: #3498db;
|
24 |
+
color: white;
|
25 |
+
border: none;
|
26 |
+
border-radius: 4px;
|
27 |
+
cursor: pointer;
|
28 |
+
font-size: 14px;
|
29 |
+
transition: background-color 0.3s;
|
30 |
+
}
|
31 |
+
#camera-toggle:hover {
|
32 |
+
background-color: #2980b9;
|
33 |
+
}
|
34 |
+
#heightmap {
|
35 |
+
display: none;
|
36 |
+
}
|
37 |
+
#loadingOverlay {
|
38 |
+
display: none;
|
39 |
+
position: fixed;
|
40 |
+
top: 0;
|
41 |
+
left: 0;
|
42 |
+
width: 100%;
|
43 |
+
height: 100%;
|
44 |
+
background-color: rgba(0, 0, 0, 0.7);
|
45 |
+
z-index: 9999;
|
46 |
+
justify-content: center;
|
47 |
+
align-items: center;
|
48 |
+
}
|
49 |
+
.loader {
|
50 |
+
border: 5px solid #f3f3f3;
|
51 |
+
border-top: 5px solid #3498db;
|
52 |
+
border-radius: 50%;
|
53 |
+
width: 50px;
|
54 |
+
height: 50px;
|
55 |
+
animation: spin 1s linear infinite;
|
56 |
+
}
|
57 |
+
@keyframes spin {
|
58 |
+
0% { transform: rotate(0deg); }
|
59 |
+
100% { transform: rotate(360deg); }
|
60 |
+
}
|
61 |
+
</style>
|
62 |
+
</head>
|
63 |
+
<body>
|
64 |
+
<input type="text" id="prompt-input" placeholder="Countryside house with trees..">
|
65 |
+
<button id="camera-toggle">Toggle Camera</button>
|
66 |
+
<div id="loadingOverlay">
|
67 |
+
<div class="loader"></div>
|
68 |
+
</div>
|
69 |
+
<canvas id="heightmap" width="256" height="256"></canvas>
|
70 |
+
<script type="importmap">
|
71 |
+
{
|
72 |
+
"imports": {
|
73 |
+
"three": "/libs/three.js/0.172.0/three.module.min.js",
|
74 |
+
"three/addons/": "/libs/three.js/0.172.0/jsm/",
|
75 |
+
"@gradio/client": "/libs/gradio-client.js/1.10.0/gradio-client.js"
|
76 |
+
}
|
77 |
+
}
|
78 |
+
</script>
|
79 |
+
<script type="module">
|
80 |
+
import * as THREE from 'three';
|
81 |
+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
82 |
+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
83 |
+
import { Client } from "@gradio/client";
|
84 |
+
|
85 |
+
|
86 |
+
let isLoading = false;
|
87 |
+
let lastPromptInputValue = "";
|
88 |
+
let isDragging = false;
|
89 |
+
let mouseDownPosition = new THREE.Vector2();
|
90 |
+
const objectCountByType = new Map();
|
91 |
+
let terrainInstances;
|
92 |
+
let objectInstances = new Map();
|
93 |
+
const TILE_SIZE = 4;
|
94 |
+
const GRID_SIZE = 10;
|
95 |
+
let currentTileType = null;
|
96 |
+
let hoveredTileIndex = -1;
|
97 |
+
const raycaster = new THREE.Raycaster();
|
98 |
+
const mouse = new THREE.Vector2();
|
99 |
+
let highlightMesh = null;
|
100 |
+
const tileTypes = new Map();
|
101 |
+
const gltfLoader = new GLTFLoader();
|
102 |
+
|
103 |
+
// Scene setup
|
104 |
+
const scene = new THREE.Scene();
|
105 |
+
scene.background = new THREE.Color(0x87CEEB);
|
106 |
+
|
107 |
+
// Camera setup with both perspectives
|
108 |
+
const frustumSize = 40;
|
109 |
+
const aspect = window.innerWidth / window.innerHeight;
|
110 |
+
|
111 |
+
const orthoCamera = new THREE.OrthographicCamera(
|
112 |
+
-20, 20, 20, -20,
|
113 |
+
0.1, 2000
|
114 |
+
);
|
115 |
+
orthoCamera.position.set(20, 20, -20);
|
116 |
+
orthoCamera.lookAt(0, 0, 0);
|
117 |
+
|
118 |
+
const perspCamera = new THREE.PerspectiveCamera(
|
119 |
+
75, window.innerWidth / window.innerHeight, 0.1, 2000
|
120 |
+
);
|
121 |
+
perspCamera.position.set(20, 20, -20);
|
122 |
+
perspCamera.lookAt(0, 0, 0);
|
123 |
+
|
124 |
+
let currentCamera = orthoCamera;
|
125 |
+
let isOrthographic = true;
|
126 |
+
|
127 |
+
// Renderer setup
|
128 |
+
const renderer = new THREE.WebGLRenderer({ antialias: true });
|
129 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
130 |
+
renderer.shadowMap.enabled = true;
|
131 |
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
132 |
+
document.body.appendChild(renderer.domElement);
|
133 |
+
|
134 |
+
// Controls
|
135 |
+
let controls = new OrbitControls(currentCamera, renderer.domElement);
|
136 |
+
controls.enableDamping = true;
|
137 |
+
|
138 |
+
// Lighting
|
139 |
+
const directionalLight = new THREE.DirectionalLight(0xffffff, 4.5); // Increased from 1.5 to 4.5
|
140 |
+
directionalLight.position.set(20, 30, 20);
|
141 |
+
directionalLight.castShadow = true;
|
142 |
+
|
143 |
+
// Improve shadow quality
|
144 |
+
directionalLight.shadow.mapSize.width = 2048;
|
145 |
+
directionalLight.shadow.mapSize.height = 2048;
|
146 |
+
directionalLight.shadow.camera.near = 0.1;
|
147 |
+
directionalLight.shadow.camera.far = 100;
|
148 |
+
directionalLight.shadow.camera.left = -30;
|
149 |
+
directionalLight.shadow.camera.right = 30;
|
150 |
+
directionalLight.shadow.camera.top = 30;
|
151 |
+
directionalLight.shadow.camera.bottom = -30;
|
152 |
+
directionalLight.shadow.bias = -0.001;
|
153 |
+
|
154 |
+
// Add a brighter ambient light
|
155 |
+
scene.add(new THREE.AmbientLight(0x404040, 1.2));
|
156 |
+
|
157 |
+
// Add lighter fog
|
158 |
+
scene.fog = new THREE.FogExp2(0x87CEEB, 0.015);
|
159 |
+
|
160 |
+
// Enable shadow mapping in the renderer
|
161 |
+
renderer.shadowMap.enabled = true;
|
162 |
+
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
163 |
+
|
164 |
+
scene.add(directionalLight);
|
165 |
+
|
166 |
+
|
167 |
+
// Height map generation
|
168 |
+
const canvasH = document.getElementById('heightmap');
|
169 |
+
const ctx = canvasH.getContext('2d');
|
170 |
+
const heightMap = new THREE.CanvasTexture(canvasH);
|
171 |
+
|
172 |
+
function createSeamlessNoise(size, octaves) {
|
173 |
+
const noise = new Array(size * size).fill(0);
|
174 |
+
|
175 |
+
function smoothStep(t) {
|
176 |
+
return t * t * (3 - 2 * t);
|
177 |
+
}
|
178 |
+
|
179 |
+
function interpolate(a, b, t) {
|
180 |
+
return a + (b - a) * smoothStep(t);
|
181 |
+
}
|
182 |
+
|
183 |
+
for (let octave = 0; octave < octaves; octave++) {
|
184 |
+
const frequency = 1 << octave;
|
185 |
+
const amplitude = 1 / (1 << octave);
|
186 |
+
|
187 |
+
const grid = new Array((frequency + 1) * (frequency + 1));
|
188 |
+
for (let i = 0; i <= frequency; i++) {
|
189 |
+
for (let j = 0; j <= frequency; j++) {
|
190 |
+
grid[i * (frequency + 1) + j] = Math.random();
|
191 |
+
}
|
192 |
+
}
|
193 |
+
|
194 |
+
for (let i = 0; i <= frequency; i++) {
|
195 |
+
grid[i * (frequency + 1) + frequency] = grid[i * (frequency + 1)];
|
196 |
+
}
|
197 |
+
for (let j = 0; j <= frequency; j++) {
|
198 |
+
grid[frequency * (frequency + 1) + j] = grid[j];
|
199 |
+
}
|
200 |
+
grid[frequency * (frequency + 1) + frequency] = grid[0];
|
201 |
+
|
202 |
+
const cellSize = size / frequency;
|
203 |
+
for (let y = 0; y < size; y++) {
|
204 |
+
for (let x = 0; x < size; x++) {
|
205 |
+
const gridX = Math.floor(x / cellSize);
|
206 |
+
const gridY = Math.floor(y / cellSize);
|
207 |
+
const fracX = (x % cellSize) / cellSize;
|
208 |
+
const fracY = (y % cellSize) / cellSize;
|
209 |
+
|
210 |
+
const v1 = grid[gridY * (frequency + 1) + gridX];
|
211 |
+
const v2 = grid[gridY * (frequency + 1) + (gridX + 1)];
|
212 |
+
const v3 = grid[(gridY + 1) * (frequency + 1) + gridX];
|
213 |
+
const v4 = grid[(gridY + 1) * (frequency + 1) + (gridX + 1)];
|
214 |
+
|
215 |
+
const i1 = interpolate(v1, v2, fracX);
|
216 |
+
const i2 = interpolate(v3, v4, fracX);
|
217 |
+
const value = interpolate(i1, i2, fracY);
|
218 |
+
|
219 |
+
noise[y * size + x] += value * amplitude;
|
220 |
+
}
|
221 |
+
}
|
222 |
+
}
|
223 |
+
|
224 |
+
return noise;
|
225 |
+
}
|
226 |
+
|
227 |
+
function createHeightMap() {
|
228 |
+
const size = 256;
|
229 |
+
const noise = createSeamlessNoise(size, 8);
|
230 |
+
|
231 |
+
const imageData = ctx.createImageData(size, size);
|
232 |
+
let min = Infinity, max = -Infinity;
|
233 |
+
|
234 |
+
for (let i = 0; i < noise.length; i++) {
|
235 |
+
min = Math.min(min, noise[i]);
|
236 |
+
max = Math.max(max, noise[i]);
|
237 |
+
}
|
238 |
+
|
239 |
+
for (let i = 0; i < noise.length; i++) {
|
240 |
+
const normalized = Math.floor(((noise[i] - min) / (max - min)) * 255);
|
241 |
+
imageData.data[i * 4] = normalized;
|
242 |
+
imageData.data[i * 4 + 1] = normalized;
|
243 |
+
imageData.data[i * 4 + 2] = normalized;
|
244 |
+
imageData.data[i * 4 + 3] = 255;
|
245 |
+
}
|
246 |
+
|
247 |
+
ctx.putImageData(imageData, 0, 0);
|
248 |
+
heightMap.needsUpdate = true;
|
249 |
+
|
250 |
+
if (terrainInstances?.material) {
|
251 |
+
terrainInstances.material.displacementMap = heightMap;
|
252 |
+
terrainInstances.material.needsUpdate = true;
|
253 |
+
}
|
254 |
+
}
|
255 |
+
|
256 |
+
// Create base terrain type
|
257 |
+
function createBaseTileType() {
|
258 |
+
const geometry = new THREE.PlaneGeometry(TILE_SIZE, TILE_SIZE, 64, 64);
|
259 |
+
const material = new THREE.MeshStandardMaterial({
|
260 |
+
color: 0x46732a,
|
261 |
+
displacementMap: heightMap,
|
262 |
+
displacementScale: 0.8,
|
263 |
+
flatShading: false,
|
264 |
+
side: THREE.DoubleSide,
|
265 |
+
shadowSide: THREE.DoubleSide
|
266 |
+
});
|
267 |
+
|
268 |
+
tileTypes.set('base', { geometry, material });
|
269 |
+
}
|
270 |
+
|
271 |
+
function createHighlightMesh() {
|
272 |
+
const geometry = new THREE.PlaneGeometry(TILE_SIZE, TILE_SIZE);
|
273 |
+
const edges = new THREE.EdgesGeometry(geometry);
|
274 |
+
const material = new THREE.LineBasicMaterial({
|
275 |
+
color: 0xffff00,
|
276 |
+
linewidth: 2,
|
277 |
+
transparent: true,
|
278 |
+
opacity: 0.9
|
279 |
+
});
|
280 |
+
|
281 |
+
highlightMesh = new THREE.LineSegments(edges, material);
|
282 |
+
highlightMesh.rotation.x = -Math.PI / 2;
|
283 |
+
highlightMesh.visible = false;
|
284 |
+
scene.add(highlightMesh);
|
285 |
+
}
|
286 |
+
|
287 |
+
function initializeTerrain() {
|
288 |
+
createBaseTileType();
|
289 |
+
createHighlightMesh();
|
290 |
+
|
291 |
+
const baseTile = tileTypes.get('base');
|
292 |
+
const instanceCount = GRID_SIZE * GRID_SIZE;
|
293 |
+
terrainInstances = new THREE.InstancedMesh(
|
294 |
+
baseTile.geometry,
|
295 |
+
baseTile.material,
|
296 |
+
instanceCount
|
297 |
+
);
|
298 |
+
terrainInstances.castShadow = true;
|
299 |
+
terrainInstances.receiveShadow = true;
|
300 |
+
|
301 |
+
const matrix = new THREE.Matrix4();
|
302 |
+
const position = new THREE.Vector3();
|
303 |
+
const rotation = new THREE.Euler();
|
304 |
+
const quaternion = new THREE.Quaternion();
|
305 |
+
const scale = new THREE.Vector3(1, 1, 1);
|
306 |
+
|
307 |
+
let index = 0;
|
308 |
+
const offset = (GRID_SIZE * TILE_SIZE) / 2 - TILE_SIZE / 2;
|
309 |
+
|
310 |
+
for (let x = 0; x < GRID_SIZE; x++) {
|
311 |
+
for (let z = 0; z < GRID_SIZE; z++) {
|
312 |
+
position.set(
|
313 |
+
x * TILE_SIZE - offset,
|
314 |
+
0,
|
315 |
+
z * TILE_SIZE - offset
|
316 |
+
);
|
317 |
+
rotation.set(-Math.PI / 2, 0, 0);
|
318 |
+
quaternion.setFromEuler(rotation);
|
319 |
+
|
320 |
+
matrix.compose(position, quaternion, scale);
|
321 |
+
terrainInstances.setMatrixAt(index, matrix);
|
322 |
+
index++;
|
323 |
+
}
|
324 |
+
}
|
325 |
+
|
326 |
+
scene.add(terrainInstances);
|
327 |
+
}
|
328 |
+
|
329 |
+
async function createGLBTileType(modelUrl, typeName) {
|
330 |
+
return new Promise((resolve, reject) => {
|
331 |
+
gltfLoader.load(modelUrl, (gltf) => {
|
332 |
+
const model = gltf.scene;
|
333 |
+
|
334 |
+
const box = new THREE.Box3().setFromObject(model);
|
335 |
+
const size = box.getSize(new THREE.Vector3());
|
336 |
+
|
337 |
+
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
338 |
+
|
339 |
+
const material = new THREE.MeshStandardMaterial({ visible: false });
|
340 |
+
|
341 |
+
tileTypes.set(typeName, {
|
342 |
+
geometry,
|
343 |
+
material,
|
344 |
+
model: model.clone()
|
345 |
+
});
|
346 |
+
|
347 |
+
resolve();
|
348 |
+
}, undefined, reject);
|
349 |
+
});
|
350 |
+
}
|
351 |
+
|
352 |
+
|
353 |
+
// First, create a function to find the Y position of the widest cross-section
|
354 |
+
function findWidestCrossSection(mesh) {
|
355 |
+
// Track the maximum width/depth we find and its Y position
|
356 |
+
let maxArea = 0;
|
357 |
+
let maxAreaY = 0;
|
358 |
+
|
359 |
+
// Get all vertices from the mesh and its children
|
360 |
+
const vertices = [];
|
361 |
+
mesh.traverse((child) => {
|
362 |
+
if (child.isMesh && child.geometry) {
|
363 |
+
const positions = child.geometry.attributes.position;
|
364 |
+
const vertexCount = positions.count;
|
365 |
+
|
366 |
+
// Transform vertices to world space
|
367 |
+
const matrix = child.matrixWorld;
|
368 |
+
for (let i = 0; i < vertexCount; i++) {
|
369 |
+
const vertex = new THREE.Vector3();
|
370 |
+
vertex.fromBufferAttribute(positions, i);
|
371 |
+
vertex.applyMatrix4(matrix);
|
372 |
+
vertices.push(vertex);
|
373 |
+
}
|
374 |
+
}
|
375 |
+
});
|
376 |
+
|
377 |
+
if (vertices.length === 0) return 0;
|
378 |
+
|
379 |
+
// Find Y range to analyze
|
380 |
+
const yValues = vertices.map(v => v.y);
|
381 |
+
const minY = Math.min(...yValues);
|
382 |
+
const maxY = Math.max(...yValues);
|
383 |
+
|
384 |
+
// Sample Y positions at regular intervals
|
385 |
+
const steps = 128; // Number of cross-sections to check
|
386 |
+
const yStep = (maxY - minY) / steps;
|
387 |
+
|
388 |
+
for (let i = 0; i <= steps; i++) {
|
389 |
+
const currentY = minY + (i * yStep);
|
390 |
+
|
391 |
+
// Find vertices near this Y level (within small threshold)
|
392 |
+
const threshold = yStep / 2;
|
393 |
+
const sectionVertices = vertices.filter(v =>
|
394 |
+
Math.abs(v.y - currentY) < threshold
|
395 |
+
);
|
396 |
+
|
397 |
+
if (sectionVertices.length > 0) {
|
398 |
+
// Calculate bounding area of this cross-section
|
399 |
+
const xValues = sectionVertices.map(v => v.x);
|
400 |
+
const zValues = sectionVertices.map(v => v.z);
|
401 |
+
|
402 |
+
const width = Math.max(...xValues) - Math.min(...xValues);
|
403 |
+
const depth = Math.max(...zValues) - Math.min(...zValues);
|
404 |
+
const area = width * depth;
|
405 |
+
|
406 |
+
if (area > maxArea) {
|
407 |
+
maxArea = area;
|
408 |
+
maxAreaY = currentY;
|
409 |
+
}
|
410 |
+
}
|
411 |
+
}
|
412 |
+
|
413 |
+
return maxAreaY;
|
414 |
+
}
|
415 |
+
|
416 |
+
|
417 |
+
function replaceTileInstance(instanceIndex, newTypeName) {
|
418 |
+
const newType = tileTypes.get(newTypeName);
|
419 |
+
if (!newType) return;
|
420 |
+
|
421 |
+
// Get original transformation matrix from terrain instance
|
422 |
+
const matrix = new THREE.Matrix4();
|
423 |
+
terrainInstances.getMatrixAt(instanceIndex, matrix);
|
424 |
+
|
425 |
+
// Extract position from matrix
|
426 |
+
const position = new THREE.Vector3();
|
427 |
+
const rotation = new THREE.Quaternion();
|
428 |
+
const scale = new THREE.Vector3();
|
429 |
+
matrix.decompose(position, rotation, scale);
|
430 |
+
|
431 |
+
// Check if there's an existing object of the same type
|
432 |
+
if (objectInstances.has(instanceIndex)) {
|
433 |
+
const existing = objectInstances.get(instanceIndex);
|
434 |
+
if (existing.typeName === newTypeName) {
|
435 |
+
// Rotate existing object's rotation group by 90 degrees
|
436 |
+
const currentRotationY = (existing.rotationY || 0) + Math.PI / 2;
|
437 |
+
const normalizedRotation = currentRotationY % (Math.PI * 2);
|
438 |
+
|
439 |
+
existing.rotationGroup.rotation.y = (Math.PI / 4) + normalizedRotation;
|
440 |
+
existing.rotationY = normalizedRotation;
|
441 |
+
return; // Exit early as we just rotated the existing object
|
442 |
+
} else {
|
443 |
+
// Remove existing object if it's a different type
|
444 |
+
scene.remove(existing.rotationGroup);
|
445 |
+
}
|
446 |
+
}
|
447 |
+
|
448 |
+
// Create new mesh from model
|
449 |
+
const newMesh = newType.model.clone();
|
450 |
+
|
451 |
+
// Create hierarchy of groups for different transformations
|
452 |
+
const rotationGroup = new THREE.Group(); // Handles user-controlled Y rotation
|
453 |
+
const orientationGroup = new THREE.Group(); // Handles initial orientation
|
454 |
+
|
455 |
+
// Build hierarchy
|
456 |
+
scene.add(rotationGroup);
|
457 |
+
rotationGroup.add(orientationGroup);
|
458 |
+
orientationGroup.add(newMesh);
|
459 |
+
|
460 |
+
// Position the top-level group at the tile location
|
461 |
+
rotationGroup.position.copy(position);
|
462 |
+
|
463 |
+
// Enable shadows
|
464 |
+
newMesh.traverse((child) => {
|
465 |
+
if (child.isMesh) {
|
466 |
+
child.castShadow = true;
|
467 |
+
child.receiveShadow = true;
|
468 |
+
}
|
469 |
+
if (child.material) {
|
470 |
+
child.material.shadowSide = THREE.DoubleSide;
|
471 |
+
child.material.needsUpdate = true;
|
472 |
+
}
|
473 |
+
});
|
474 |
+
|
475 |
+
|
476 |
+
// Create a bounding box to calculate the mesh's dimensions
|
477 |
+
const bbox = new THREE.Box3().setFromObject(newMesh);
|
478 |
+
const meshCenter = bbox.getCenter(new THREE.Vector3());
|
479 |
+
|
480 |
+
// Center the mesh on its local origin
|
481 |
+
newMesh.position.sub(meshCenter);
|
482 |
+
|
483 |
+
// Apply initial orientation using clean, separate rotations
|
484 |
+
orientationGroup.rotation.x = 0.6; // fix mesh generation tiling
|
485 |
+
orientationGroup.rotation.y = 0;
|
486 |
+
orientationGroup.rotation.z = 0;
|
487 |
+
|
488 |
+
// Find the Y position of widest cross-section and adjust position
|
489 |
+
const baseY = findWidestCrossSection(newMesh);
|
490 |
+
newMesh.position.y -= baseY;
|
491 |
+
newMesh.position.y += 1.2;
|
492 |
+
|
493 |
+
rotationGroup.rotation.y = Math.PI / 4;
|
494 |
+
|
495 |
+
// Apply scale to the mesh
|
496 |
+
newMesh.scale.set(5.6, 5.6, 5.6);
|
497 |
+
|
498 |
+
// Adjust XZ position to ensure centering after rotation
|
499 |
+
bbox.setFromObject(newMesh);
|
500 |
+
const rotatedCenter = bbox.getCenter(new THREE.Vector3());
|
501 |
+
//newMesh.position.x -= 1.0;//rotatedCenter.x;
|
502 |
+
//newMesh.position.z -= 0;//rotatedCenter.z;
|
503 |
+
|
504 |
+
|
505 |
+
// Add new object to scene and track it with metadata
|
506 |
+
objectInstances.set(instanceIndex, {
|
507 |
+
rotationGroup: rotationGroup,
|
508 |
+
orientationGroup: orientationGroup,
|
509 |
+
mesh: newMesh,
|
510 |
+
typeName: newTypeName,
|
511 |
+
rotationY: 0 // Start with 0 rotation for the group
|
512 |
+
});
|
513 |
+
|
514 |
+
const currentCount = objectCountByType.get(newTypeName) || 0;
|
515 |
+
objectCountByType.set(newTypeName, currentCount + 1);
|
516 |
+
}
|
517 |
+
|
518 |
+
let gradioClient = null;
|
519 |
+
|
520 |
+
async function initializeGradioClient() {
|
521 |
+
try {
|
522 |
+
gradioClient = await Client.connect("jbilcke-hf/text-to-3d");
|
523 |
+
} catch (error) {
|
524 |
+
console.error("Failed to connect to Gradio:", error);
|
525 |
+
}
|
526 |
+
}
|
527 |
+
|
528 |
+
async function generateTile(prompt) {
|
529 |
+
if (!gradioClient) {
|
530 |
+
await initializeGradioClient();
|
531 |
+
}
|
532 |
+
|
533 |
+
if (!gradioClient) {
|
534 |
+
console.error("Gradio client not initialized");
|
535 |
+
return;
|
536 |
+
}
|
537 |
+
|
538 |
+
if (prompt === lastPromptInputValue) {
|
539 |
+
return;
|
540 |
+
}
|
541 |
+
|
542 |
+
lastPromptInputValue = prompt;
|
543 |
+
|
544 |
+
try {
|
545 |
+
isLoading = true;
|
546 |
+
loadingOverlay.style.display = 'flex';
|
547 |
+
|
548 |
+
|
549 |
+
const result = await gradioClient.predict("/generate", {
|
550 |
+
prompt: `isometric videogame asset, ${prompt.trim()}`,
|
551 |
+
});
|
552 |
+
|
553 |
+
const modelUrl = result.data[0].url;
|
554 |
+
|
555 |
+
|
556 |
+
//const modelUrl = "/models/eiffel_tower.glb";
|
557 |
+
const tileTypeName = `custom_${Date.now()}`;
|
558 |
+
await createGLBTileType(modelUrl, tileTypeName);
|
559 |
+
|
560 |
+
// Instead of random placement, store the current tile type
|
561 |
+
currentTileType = tileTypeName;
|
562 |
+
|
563 |
+
// Optional: place one instance to show the new type
|
564 |
+
replaceTileInstance(Math.floor(GRID_SIZE * GRID_SIZE / 2), tileTypeName);
|
565 |
+
|
566 |
+
} catch (error) {
|
567 |
+
console.error("Failed to generate/load terrain:", error);
|
568 |
+
} finally {
|
569 |
+
isLoading = false;
|
570 |
+
loadingOverlay.style.display = 'none';
|
571 |
+
}
|
572 |
+
}
|
573 |
+
|
574 |
+
|
575 |
+
// Handle window resize
|
576 |
+
window.addEventListener('resize', () => {
|
577 |
+
const aspect = window.innerWidth / window.innerHeight;
|
578 |
+
const frustumSize = 40;
|
579 |
+
camera.left = -frustumSize * aspect / 2;
|
580 |
+
camera.right = frustumSize * aspect / 2;
|
581 |
+
camera.top = frustumSize / 2;
|
582 |
+
camera.bottom = -frustumSize / 2;
|
583 |
+
camera.updateProjectionMatrix();
|
584 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
585 |
+
});
|
586 |
+
|
587 |
+
// Text input handling
|
588 |
+
const promptInput = document.getElementById('prompt-input');
|
589 |
+
|
590 |
+
// Event listeners
|
591 |
+
promptInput.addEventListener('keypress', async (e) => {
|
592 |
+
if (e.key === 'Enter') {
|
593 |
+
await generateTile(promptInput.value);
|
594 |
+
}
|
595 |
+
});
|
596 |
+
|
597 |
+
promptInput.addEventListener('blur', async () => {
|
598 |
+
await generateTile(promptInput.value);
|
599 |
+
});
|
600 |
+
|
601 |
+
canvasH.addEventListener('click', createHeightMap);
|
602 |
+
|
603 |
+
// Initialize and start
|
604 |
+
createHeightMap();
|
605 |
+
initializeTerrain();
|
606 |
+
|
607 |
+
|
608 |
+
function onMouseDown(event) {
|
609 |
+
if (event.button === 0) { // Left click
|
610 |
+
mouseDownPosition.x = event.clientX;
|
611 |
+
mouseDownPosition.y = event.clientY;
|
612 |
+
isDragging = false;
|
613 |
+
}
|
614 |
+
}
|
615 |
+
|
616 |
+
|
617 |
+
|
618 |
+
// Update raycasting to use current camera
|
619 |
+
function onMouseMove(event) {
|
620 |
+
if (event.buttons === 1) {
|
621 |
+
const deltaX = Math.abs(event.clientX - mouseDownPosition.x);
|
622 |
+
const deltaY = Math.abs(event.clientY - mouseDownPosition.y);
|
623 |
+
if (deltaX > 5 || deltaY > 5) {
|
624 |
+
isDragging = true;
|
625 |
+
}
|
626 |
+
}
|
627 |
+
|
628 |
+
mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
|
629 |
+
mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;
|
630 |
+
|
631 |
+
raycaster.setFromCamera(mouse, currentCamera);
|
632 |
+
const intersects = raycaster.intersectObject(terrainInstances);
|
633 |
+
|
634 |
+
if (intersects.length > 0) {
|
635 |
+
const instanceId = intersects[0].instanceId;
|
636 |
+
|
637 |
+
if (instanceId !== hoveredTileIndex) {
|
638 |
+
hoveredTileIndex = instanceId;
|
639 |
+
|
640 |
+
const matrix = new THREE.Matrix4();
|
641 |
+
terrainInstances.getMatrixAt(instanceId, matrix);
|
642 |
+
const position = new THREE.Vector3();
|
643 |
+
matrix.decompose(position, new THREE.Quaternion(), new THREE.Vector3());
|
644 |
+
|
645 |
+
highlightMesh.position.set(position.x, 0.4, position.z);
|
646 |
+
highlightMesh.visible = true;
|
647 |
+
}
|
648 |
+
} else {
|
649 |
+
hoveredTileIndex = -1;
|
650 |
+
highlightMesh.visible = false;
|
651 |
+
}
|
652 |
+
}
|
653 |
+
|
654 |
+
function onMouseUp(event) {
|
655 |
+
if (event.button === 0) { // Left click
|
656 |
+
if (!isDragging && hoveredTileIndex !== -1 && currentTileType) {
|
657 |
+
replaceTileInstance(hoveredTileIndex, currentTileType);
|
658 |
+
}
|
659 |
+
}
|
660 |
+
}
|
661 |
+
|
662 |
+
function onClick(event) {
|
663 |
+
if (event.button === 2) { // Right click
|
664 |
+
if (hoveredTileIndex !== -1 && objectInstances.has(hoveredTileIndex)) {
|
665 |
+
const existing = objectInstances.get(hoveredTileIndex);
|
666 |
+
const typeName = existing.typeName;
|
667 |
+
if (typeName) {
|
668 |
+
const currentCount = objectCountByType.get(typeName) || 0;
|
669 |
+
if (currentCount > 1) {
|
670 |
+
objectCountByType.set(typeName, currentCount - 1);
|
671 |
+
} else {
|
672 |
+
objectCountByType.delete(typeName);
|
673 |
+
}
|
674 |
+
}
|
675 |
+
scene.remove(existing.rotationGroup);
|
676 |
+
objectInstances.delete(hoveredTileIndex);
|
677 |
+
}
|
678 |
+
}
|
679 |
+
}
|
680 |
+
|
681 |
+
|
682 |
+
// Add camera toggle button handler
|
683 |
+
const cameraToggle = document.getElementById('camera-toggle');
|
684 |
+
cameraToggle.addEventListener('click', () => {
|
685 |
+
isOrthographic = !isOrthographic;
|
686 |
+
currentCamera = isOrthographic ? orthoCamera : perspCamera;
|
687 |
+
|
688 |
+
// Update controls
|
689 |
+
controls.dispose();
|
690 |
+
controls = new OrbitControls(currentCamera, renderer.domElement);
|
691 |
+
controls.enableDamping = true;
|
692 |
+
|
693 |
+
// Sync camera positions
|
694 |
+
const oldPos = isOrthographic ? perspCamera.position : orthoCamera.position;
|
695 |
+
currentCamera.position.copy(oldPos);
|
696 |
+
currentCamera.lookAt(controls.target);
|
697 |
+
|
698 |
+
// Update projection if needed
|
699 |
+
if (!isOrthographic) {
|
700 |
+
perspCamera.aspect = window.innerWidth / window.innerHeight;
|
701 |
+
perspCamera.updateProjectionMatrix();
|
702 |
+
}
|
703 |
+
});
|
704 |
+
|
705 |
+
// Update window resize handler
|
706 |
+
window.addEventListener('resize', () => {
|
707 |
+
const aspect = window.innerWidth / window.innerHeight;
|
708 |
+
if (isOrthographic) {
|
709 |
+
const frustumSize = 40;
|
710 |
+
orthoCamera.left = -frustumSize * aspect / 2;
|
711 |
+
orthoCamera.right = frustumSize * aspect / 2;
|
712 |
+
orthoCamera.top = frustumSize / 2;
|
713 |
+
orthoCamera.bottom = -frustumSize / 2;
|
714 |
+
orthoCamera.updateProjectionMatrix();
|
715 |
+
} else {
|
716 |
+
perspCamera.aspect = aspect;
|
717 |
+
perspCamera.updateProjectionMatrix();
|
718 |
+
}
|
719 |
+
renderer.setSize(window.innerWidth, window.innerHeight);
|
720 |
+
});
|
721 |
+
|
722 |
+
renderer.domElement.removeEventListener('click', onClick);
|
723 |
+
renderer.domElement.addEventListener('mousedown', onMouseDown);
|
724 |
+
renderer.domElement.addEventListener('mousemove', onMouseMove);
|
725 |
+
renderer.domElement.addEventListener('mouseup', onMouseUp);
|
726 |
+
renderer.domElement.addEventListener('contextmenu', (event) => {
|
727 |
+
event.preventDefault();
|
728 |
+
onClick(event);
|
729 |
+
});
|
730 |
+
|
731 |
+
|
732 |
+
function animate() {
|
733 |
+
requestAnimationFrame(animate);
|
734 |
+
controls.update();
|
735 |
+
renderer.render(scene, currentCamera);
|
736 |
+
}
|
737 |
+
|
738 |
+
animate();
|
739 |
+
</script>
|
740 |
+
</body>
|
741 |
+
</html>
|
libs/gradio-client.js/1.10.0/gradio-client.js
ADDED
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Bundled by jsDelivr using Rollup v2.79.2 and Terser v5.37.0.
|
3 |
+
* Original file: /npm/@gradio/[email protected]/dist/index.js
|
4 |
+
*
|
5 |
+
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
|
6 |
+
*/
|
7 |
+
var t="undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{};function e(){throw new Error("setTimeout has not been defined")}function n(){throw new Error("clearTimeout has not been defined")}var r=e,i=n;function o(t){if(r===setTimeout)return setTimeout(t,0);if((r===e||!r)&&setTimeout)return r=setTimeout,setTimeout(t,0);try{return r(t,0)}catch(e){try{return r.call(null,t,0)}catch(e){return r.call(this,t,0)}}}"function"==typeof t.setTimeout&&(r=setTimeout),"function"==typeof t.clearTimeout&&(i=clearTimeout);var s,a=[],u=!1,c=-1;function h(){u&&s&&(u=!1,s.length?a=s.concat(a):c=-1,a.length&&l())}function l(){if(!u){var t=o(h);u=!0;for(var e=a.length;e;){for(s=a,a=[];++c<e;)s&&s[c].run();c=-1,e=a.length}s=null,u=!1,function(t){if(i===clearTimeout)return clearTimeout(t);if((i===n||!i)&&clearTimeout)return i=clearTimeout,clearTimeout(t);try{return i(t)}catch(e){try{return i.call(null,t)}catch(e){return i.call(this,t)}}}(t)}}function f(t,e){this.fun=t,this.array=e}f.prototype.run=function(){this.fun.apply(null,this.array)};function p(){}var d=p,g=p,w=p,m=p,_=p,y=p,v=p;var b=t.performance||{},E=b.now||b.mozNow||b.msNow||b.oNow||b.webkitNow||function(){return(new Date).getTime()};var S=new Date;var A={nextTick:function(t){var e=new Array(arguments.length-1);if(arguments.length>1)for(var n=1;n<arguments.length;n++)e[n-1]=arguments[n];a.push(new f(t,e)),1!==a.length||u||o(l)},title:"browser",browser:!0,env:{},argv:[],version:"",versions:{},on:d,addListener:g,once:w,off:m,removeListener:_,removeAllListeners:y,emit:v,binding:function(t){throw new Error("process.binding is not supported")},cwd:function(){return"/"},chdir:function(t){throw new Error("process.chdir is not supported")},umask:function(){return 0},hrtime:function(t){var e=.001*E.call(b),n=Math.floor(e),r=Math.floor(e%1*1e9);return t&&(n-=t[0],(r-=t[1])<0&&(n--,r+=1e9)),[n,r]},platform:"browser",release:{},config:{},uptime:function(){return(new Date-S)/1e3}},T=[],P=[],R="undefined"!=typeof Uint8Array?Uint8Array:Array,x=!1;function k(){x=!0;for(var t="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",e=0;e<64;++e)T[e]=t[e],P[t.charCodeAt(e)]=e;P["-".charCodeAt(0)]=62,P["_".charCodeAt(0)]=63}function $(t,e,n){for(var r,i,o=[],s=e;s<n;s+=3)r=(t[s]<<16)+(t[s+1]<<8)+t[s+2],o.push(T[(i=r)>>18&63]+T[i>>12&63]+T[i>>6&63]+T[63&i]);return o.join("")}function U(t){var e;x||k();for(var n=t.length,r=n%3,i="",o=[],s=16383,a=0,u=n-r;a<u;a+=s)o.push($(t,a,a+s>u?u:a+s));return 1===r?(e=t[n-1],i+=T[e>>2],i+=T[e<<4&63],i+="=="):2===r&&(e=(t[n-2]<<8)+t[n-1],i+=T[e>>10],i+=T[e>>4&63],i+=T[e<<2&63],i+="="),o.push(i),o.join("")}function O(t,e,n,r,i){var o,s,a=8*i-r-1,u=(1<<a)-1,c=u>>1,h=-7,l=n?i-1:0,f=n?-1:1,p=t[e+l];for(l+=f,o=p&(1<<-h)-1,p>>=-h,h+=a;h>0;o=256*o+t[e+l],l+=f,h-=8);for(s=o&(1<<-h)-1,o>>=-h,h+=r;h>0;s=256*s+t[e+l],l+=f,h-=8);if(0===o)o=1-c;else{if(o===u)return s?NaN:1/0*(p?-1:1);s+=Math.pow(2,r),o-=c}return(p?-1:1)*s*Math.pow(2,o-r)}function B(t,e,n,r,i,o){var s,a,u,c=8*o-i-1,h=(1<<c)-1,l=h>>1,f=23===i?Math.pow(2,-24)-Math.pow(2,-77):0,p=r?0:o-1,d=r?1:-1,g=e<0||0===e&&1/e<0?1:0;for(e=Math.abs(e),isNaN(e)||e===1/0?(a=isNaN(e)?1:0,s=h):(s=Math.floor(Math.log(e)/Math.LN2),e*(u=Math.pow(2,-s))<1&&(s--,u*=2),(e+=s+l>=1?f/u:f*Math.pow(2,1-l))*u>=2&&(s++,u/=2),s+l>=h?(a=0,s=h):s+l>=1?(a=(e*u-1)*Math.pow(2,i),s+=l):(a=e*Math.pow(2,l-1)*Math.pow(2,i),s=0));i>=8;t[n+p]=255&a,p+=d,a/=256,i-=8);for(s=s<<i|a,c+=i;c>0;t[n+p]=255&s,p+=d,s/=256,c-=8);t[n+p-d]|=128*g}var C={}.toString,D=Array.isArray||function(t){return"[object Array]"==C.call(t)};function I(){return j.TYPED_ARRAY_SUPPORT?2147483647:1073741823}function N(t,e){if(I()<e)throw new RangeError("Invalid typed array length");return j.TYPED_ARRAY_SUPPORT?(t=new Uint8Array(e)).__proto__=j.prototype:(null===t&&(t=new j(e)),t.length=e),t}function j(t,e,n){if(!(j.TYPED_ARRAY_SUPPORT||this instanceof j))return new j(t,e,n);if("number"==typeof t){if("string"==typeof e)throw new Error("If encoding is specified then the first argument must be a string");return q(this,t)}return L(this,t,e,n)}function L(t,e,n,r){if("number"==typeof e)throw new TypeError('"value" argument must not be a number');return"undefined"!=typeof ArrayBuffer&&e instanceof ArrayBuffer?function(t,e,n,r){if(e.byteLength,n<0||e.byteLength<n)throw new RangeError("'offset' is out of bounds");if(e.byteLength<n+(r||0))throw new RangeError("'length' is out of bounds");e=void 0===n&&void 0===r?new Uint8Array(e):void 0===r?new Uint8Array(e,n):new Uint8Array(e,n,r);j.TYPED_ARRAY_SUPPORT?(t=e).__proto__=j.prototype:t=Y(t,e);return t}(t,e,n,r):"string"==typeof e?function(t,e,n){"string"==typeof n&&""!==n||(n="utf8");if(!j.isEncoding(n))throw new TypeError('"encoding" must be a valid string encoding');var r=0|W(e,n);t=N(t,r);var i=t.write(e,n);i!==r&&(t=t.slice(0,i));return t}(t,e,n):function(t,e){if(F(e)){var n=0|M(e.length);return 0===(t=N(t,n)).length||e.copy(t,0,0,n),t}if(e){if("undefined"!=typeof ArrayBuffer&&e.buffer instanceof ArrayBuffer||"length"in e)return"number"!=typeof e.length||(r=e.length)!=r?N(t,0):Y(t,e);if("Buffer"===e.type&&D(e.data))return Y(t,e.data)}var r;throw new TypeError("First argument must be a string, Buffer, ArrayBuffer, Array, or array-like object.")}(t,e)}function z(t){if("number"!=typeof t)throw new TypeError('"size" argument must be a number');if(t<0)throw new RangeError('"size" argument must not be negative')}function q(t,e){if(z(e),t=N(t,e<0?0:0|M(e)),!j.TYPED_ARRAY_SUPPORT)for(var n=0;n<e;++n)t[n]=0;return t}function Y(t,e){var n=e.length<0?0:0|M(e.length);t=N(t,n);for(var r=0;r<n;r+=1)t[r]=255&e[r];return t}function M(t){if(t>=I())throw new RangeError("Attempt to allocate Buffer larger than maximum size: 0x"+I().toString(16)+" bytes");return 0|t}function F(t){return!(null==t||!t._isBuffer)}function W(t,e){if(F(t))return t.length;if("undefined"!=typeof ArrayBuffer&&"function"==typeof ArrayBuffer.isView&&(ArrayBuffer.isView(t)||t instanceof ArrayBuffer))return t.byteLength;"string"!=typeof t&&(t=""+t);var n=t.length;if(0===n)return 0;for(var r=!1;;)switch(e){case"ascii":case"latin1":case"binary":return n;case"utf8":case"utf-8":case void 0:return _t(t).length;case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return 2*n;case"hex":return n>>>1;case"base64":return yt(t).length;default:if(r)return _t(t).length;e=(""+e).toLowerCase(),r=!0}}function J(t,e,n){var r=!1;if((void 0===e||e<0)&&(e=0),e>this.length)return"";if((void 0===n||n>this.length)&&(n=this.length),n<=0)return"";if((n>>>=0)<=(e>>>=0))return"";for(t||(t="utf8");;)switch(t){case"hex":return at(this,e,n);case"utf8":case"utf-8":return rt(this,e,n);case"ascii":return ot(this,e,n);case"latin1":case"binary":return st(this,e,n);case"base64":return nt(this,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return ut(this,e,n);default:if(r)throw new TypeError("Unknown encoding: "+t);t=(t+"").toLowerCase(),r=!0}}function G(t,e,n){var r=t[e];t[e]=t[n],t[n]=r}function H(t,e,n,r,i){if(0===t.length)return-1;if("string"==typeof n?(r=n,n=0):n>2147483647?n=2147483647:n<-2147483648&&(n=-2147483648),n=+n,isNaN(n)&&(n=i?0:t.length-1),n<0&&(n=t.length+n),n>=t.length){if(i)return-1;n=t.length-1}else if(n<0){if(!i)return-1;n=0}if("string"==typeof e&&(e=j.from(e,r)),F(e))return 0===e.length?-1:V(t,e,n,r,i);if("number"==typeof e)return e&=255,j.TYPED_ARRAY_SUPPORT&&"function"==typeof Uint8Array.prototype.indexOf?i?Uint8Array.prototype.indexOf.call(t,e,n):Uint8Array.prototype.lastIndexOf.call(t,e,n):V(t,[e],n,r,i);throw new TypeError("val must be string, number or Buffer")}function V(t,e,n,r,i){var o,s=1,a=t.length,u=e.length;if(void 0!==r&&("ucs2"===(r=String(r).toLowerCase())||"ucs-2"===r||"utf16le"===r||"utf-16le"===r)){if(t.length<2||e.length<2)return-1;s=2,a/=2,u/=2,n/=2}function c(t,e){return 1===s?t[e]:t.readUInt16BE(e*s)}if(i){var h=-1;for(o=n;o<a;o++)if(c(t,o)===c(e,-1===h?0:o-h)){if(-1===h&&(h=o),o-h+1===u)return h*s}else-1!==h&&(o-=o-h),h=-1}else for(n+u>a&&(n=a-u),o=n;o>=0;o--){for(var l=!0,f=0;f<u;f++)if(c(t,o+f)!==c(e,f)){l=!1;break}if(l)return o}return-1}function Z(t,e,n,r){n=Number(n)||0;var i=t.length-n;r?(r=Number(r))>i&&(r=i):r=i;var o=e.length;if(o%2!=0)throw new TypeError("Invalid hex string");r>o/2&&(r=o/2);for(var s=0;s<r;++s){var a=parseInt(e.substr(2*s,2),16);if(isNaN(a))return s;t[n+s]=a}return s}function K(t,e,n,r){return vt(_t(e,t.length-n),t,n,r)}function Q(t,e,n,r){return vt(function(t){for(var e=[],n=0;n<t.length;++n)e.push(255&t.charCodeAt(n));return e}(e),t,n,r)}function X(t,e,n,r){return Q(t,e,n,r)}function tt(t,e,n,r){return vt(yt(e),t,n,r)}function et(t,e,n,r){return vt(function(t,e){for(var n,r,i,o=[],s=0;s<t.length&&!((e-=2)<0);++s)r=(n=t.charCodeAt(s))>>8,i=n%256,o.push(i),o.push(r);return o}(e,t.length-n),t,n,r)}function nt(t,e,n){return 0===e&&n===t.length?U(t):U(t.slice(e,n))}function rt(t,e,n){n=Math.min(t.length,n);for(var r=[],i=e;i<n;){var o,s,a,u,c=t[i],h=null,l=c>239?4:c>223?3:c>191?2:1;if(i+l<=n)switch(l){case 1:c<128&&(h=c);break;case 2:128==(192&(o=t[i+1]))&&(u=(31&c)<<6|63&o)>127&&(h=u);break;case 3:o=t[i+1],s=t[i+2],128==(192&o)&&128==(192&s)&&(u=(15&c)<<12|(63&o)<<6|63&s)>2047&&(u<55296||u>57343)&&(h=u);break;case 4:o=t[i+1],s=t[i+2],a=t[i+3],128==(192&o)&&128==(192&s)&&128==(192&a)&&(u=(15&c)<<18|(63&o)<<12|(63&s)<<6|63&a)>65535&&u<1114112&&(h=u)}null===h?(h=65533,l=1):h>65535&&(h-=65536,r.push(h>>>10&1023|55296),h=56320|1023&h),r.push(h),i+=l}return function(t){var e=t.length;if(e<=it)return String.fromCharCode.apply(String,t);var n="",r=0;for(;r<e;)n+=String.fromCharCode.apply(String,t.slice(r,r+=it));return n}(r)}j.TYPED_ARRAY_SUPPORT=void 0===t.TYPED_ARRAY_SUPPORT||t.TYPED_ARRAY_SUPPORT,I(),j.poolSize=8192,j._augment=function(t){return t.__proto__=j.prototype,t},j.from=function(t,e,n){return L(null,t,e,n)},j.TYPED_ARRAY_SUPPORT&&(j.prototype.__proto__=Uint8Array.prototype,j.__proto__=Uint8Array,"undefined"!=typeof Symbol&&Symbol.species&&j[Symbol.species]),j.alloc=function(t,e,n){return function(t,e,n,r){return z(e),e<=0?N(t,e):void 0!==n?"string"==typeof r?N(t,e).fill(n,r):N(t,e).fill(n):N(t,e)}(null,t,e,n)},j.allocUnsafe=function(t){return q(null,t)},j.allocUnsafeSlow=function(t){return q(null,t)},j.isBuffer=function(t){return null!=t&&(!!t._isBuffer||bt(t)||function(t){return"function"==typeof t.readFloatLE&&"function"==typeof t.slice&&bt(t.slice(0,0))}(t))},j.compare=function(t,e){if(!F(t)||!F(e))throw new TypeError("Arguments must be Buffers");if(t===e)return 0;for(var n=t.length,r=e.length,i=0,o=Math.min(n,r);i<o;++i)if(t[i]!==e[i]){n=t[i],r=e[i];break}return n<r?-1:r<n?1:0},j.isEncoding=function(t){switch(String(t).toLowerCase()){case"hex":case"utf8":case"utf-8":case"ascii":case"latin1":case"binary":case"base64":case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return!0;default:return!1}},j.concat=function(t,e){if(!D(t))throw new TypeError('"list" argument must be an Array of Buffers');if(0===t.length)return j.alloc(0);var n;if(void 0===e)for(e=0,n=0;n<t.length;++n)e+=t[n].length;var r=j.allocUnsafe(e),i=0;for(n=0;n<t.length;++n){var o=t[n];if(!F(o))throw new TypeError('"list" argument must be an Array of Buffers');o.copy(r,i),i+=o.length}return r},j.byteLength=W,j.prototype._isBuffer=!0,j.prototype.swap16=function(){var t=this.length;if(t%2!=0)throw new RangeError("Buffer size must be a multiple of 16-bits");for(var e=0;e<t;e+=2)G(this,e,e+1);return this},j.prototype.swap32=function(){var t=this.length;if(t%4!=0)throw new RangeError("Buffer size must be a multiple of 32-bits");for(var e=0;e<t;e+=4)G(this,e,e+3),G(this,e+1,e+2);return this},j.prototype.swap64=function(){var t=this.length;if(t%8!=0)throw new RangeError("Buffer size must be a multiple of 64-bits");for(var e=0;e<t;e+=8)G(this,e,e+7),G(this,e+1,e+6),G(this,e+2,e+5),G(this,e+3,e+4);return this},j.prototype.toString=function(){var t=0|this.length;return 0===t?"":0===arguments.length?rt(this,0,t):J.apply(this,arguments)},j.prototype.equals=function(t){if(!F(t))throw new TypeError("Argument must be a Buffer");return this===t||0===j.compare(this,t)},j.prototype.inspect=function(){var t="";return this.length>0&&(t=this.toString("hex",0,50).match(/.{2}/g).join(" "),this.length>50&&(t+=" ... ")),"<Buffer "+t+">"},j.prototype.compare=function(t,e,n,r,i){if(!F(t))throw new TypeError("Argument must be a Buffer");if(void 0===e&&(e=0),void 0===n&&(n=t?t.length:0),void 0===r&&(r=0),void 0===i&&(i=this.length),e<0||n>t.length||r<0||i>this.length)throw new RangeError("out of range index");if(r>=i&&e>=n)return 0;if(r>=i)return-1;if(e>=n)return 1;if(this===t)return 0;for(var o=(i>>>=0)-(r>>>=0),s=(n>>>=0)-(e>>>=0),a=Math.min(o,s),u=this.slice(r,i),c=t.slice(e,n),h=0;h<a;++h)if(u[h]!==c[h]){o=u[h],s=c[h];break}return o<s?-1:s<o?1:0},j.prototype.includes=function(t,e,n){return-1!==this.indexOf(t,e,n)},j.prototype.indexOf=function(t,e,n){return H(this,t,e,n,!0)},j.prototype.lastIndexOf=function(t,e,n){return H(this,t,e,n,!1)},j.prototype.write=function(t,e,n,r){if(void 0===e)r="utf8",n=this.length,e=0;else if(void 0===n&&"string"==typeof e)r=e,n=this.length,e=0;else{if(!isFinite(e))throw new Error("Buffer.write(string, encoding, offset[, length]) is no longer supported");e|=0,isFinite(n)?(n|=0,void 0===r&&(r="utf8")):(r=n,n=void 0)}var i=this.length-e;if((void 0===n||n>i)&&(n=i),t.length>0&&(n<0||e<0)||e>this.length)throw new RangeError("Attempt to write outside buffer bounds");r||(r="utf8");for(var o=!1;;)switch(r){case"hex":return Z(this,t,e,n);case"utf8":case"utf-8":return K(this,t,e,n);case"ascii":return Q(this,t,e,n);case"latin1":case"binary":return X(this,t,e,n);case"base64":return tt(this,t,e,n);case"ucs2":case"ucs-2":case"utf16le":case"utf-16le":return et(this,t,e,n);default:if(o)throw new TypeError("Unknown encoding: "+r);r=(""+r).toLowerCase(),o=!0}},j.prototype.toJSON=function(){return{type:"Buffer",data:Array.prototype.slice.call(this._arr||this,0)}};var it=4096;function ot(t,e,n){var r="";n=Math.min(t.length,n);for(var i=e;i<n;++i)r+=String.fromCharCode(127&t[i]);return r}function st(t,e,n){var r="";n=Math.min(t.length,n);for(var i=e;i<n;++i)r+=String.fromCharCode(t[i]);return r}function at(t,e,n){var r=t.length;(!e||e<0)&&(e=0),(!n||n<0||n>r)&&(n=r);for(var i="",o=e;o<n;++o)i+=mt(t[o]);return i}function ut(t,e,n){for(var r=t.slice(e,n),i="",o=0;o<r.length;o+=2)i+=String.fromCharCode(r[o]+256*r[o+1]);return i}function ct(t,e,n){if(t%1!=0||t<0)throw new RangeError("offset is not uint");if(t+e>n)throw new RangeError("Trying to access beyond buffer length")}function ht(t,e,n,r,i,o){if(!F(t))throw new TypeError('"buffer" argument must be a Buffer instance');if(e>i||e<o)throw new RangeError('"value" argument is out of bounds');if(n+r>t.length)throw new RangeError("Index out of range")}function lt(t,e,n,r){e<0&&(e=65535+e+1);for(var i=0,o=Math.min(t.length-n,2);i<o;++i)t[n+i]=(e&255<<8*(r?i:1-i))>>>8*(r?i:1-i)}function ft(t,e,n,r){e<0&&(e=4294967295+e+1);for(var i=0,o=Math.min(t.length-n,4);i<o;++i)t[n+i]=e>>>8*(r?i:3-i)&255}function pt(t,e,n,r,i,o){if(n+r>t.length)throw new RangeError("Index out of range");if(n<0)throw new RangeError("Index out of range")}function dt(t,e,n,r,i){return i||pt(t,0,n,4),B(t,e,n,r,23,4),n+4}function gt(t,e,n,r,i){return i||pt(t,0,n,8),B(t,e,n,r,52,8),n+8}j.prototype.slice=function(t,e){var n,r=this.length;if((t=~~t)<0?(t+=r)<0&&(t=0):t>r&&(t=r),(e=void 0===e?r:~~e)<0?(e+=r)<0&&(e=0):e>r&&(e=r),e<t&&(e=t),j.TYPED_ARRAY_SUPPORT)(n=this.subarray(t,e)).__proto__=j.prototype;else{var i=e-t;n=new j(i,void 0);for(var o=0;o<i;++o)n[o]=this[o+t]}return n},j.prototype.readUIntLE=function(t,e,n){t|=0,e|=0,n||ct(t,e,this.length);for(var r=this[t],i=1,o=0;++o<e&&(i*=256);)r+=this[t+o]*i;return r},j.prototype.readUIntBE=function(t,e,n){t|=0,e|=0,n||ct(t,e,this.length);for(var r=this[t+--e],i=1;e>0&&(i*=256);)r+=this[t+--e]*i;return r},j.prototype.readUInt8=function(t,e){return e||ct(t,1,this.length),this[t]},j.prototype.readUInt16LE=function(t,e){return e||ct(t,2,this.length),this[t]|this[t+1]<<8},j.prototype.readUInt16BE=function(t,e){return e||ct(t,2,this.length),this[t]<<8|this[t+1]},j.prototype.readUInt32LE=function(t,e){return e||ct(t,4,this.length),(this[t]|this[t+1]<<8|this[t+2]<<16)+16777216*this[t+3]},j.prototype.readUInt32BE=function(t,e){return e||ct(t,4,this.length),16777216*this[t]+(this[t+1]<<16|this[t+2]<<8|this[t+3])},j.prototype.readIntLE=function(t,e,n){t|=0,e|=0,n||ct(t,e,this.length);for(var r=this[t],i=1,o=0;++o<e&&(i*=256);)r+=this[t+o]*i;return r>=(i*=128)&&(r-=Math.pow(2,8*e)),r},j.prototype.readIntBE=function(t,e,n){t|=0,e|=0,n||ct(t,e,this.length);for(var r=e,i=1,o=this[t+--r];r>0&&(i*=256);)o+=this[t+--r]*i;return o>=(i*=128)&&(o-=Math.pow(2,8*e)),o},j.prototype.readInt8=function(t,e){return e||ct(t,1,this.length),128&this[t]?-1*(255-this[t]+1):this[t]},j.prototype.readInt16LE=function(t,e){e||ct(t,2,this.length);var n=this[t]|this[t+1]<<8;return 32768&n?4294901760|n:n},j.prototype.readInt16BE=function(t,e){e||ct(t,2,this.length);var n=this[t+1]|this[t]<<8;return 32768&n?4294901760|n:n},j.prototype.readInt32LE=function(t,e){return e||ct(t,4,this.length),this[t]|this[t+1]<<8|this[t+2]<<16|this[t+3]<<24},j.prototype.readInt32BE=function(t,e){return e||ct(t,4,this.length),this[t]<<24|this[t+1]<<16|this[t+2]<<8|this[t+3]},j.prototype.readFloatLE=function(t,e){return e||ct(t,4,this.length),O(this,t,!0,23,4)},j.prototype.readFloatBE=function(t,e){return e||ct(t,4,this.length),O(this,t,!1,23,4)},j.prototype.readDoubleLE=function(t,e){return e||ct(t,8,this.length),O(this,t,!0,52,8)},j.prototype.readDoubleBE=function(t,e){return e||ct(t,8,this.length),O(this,t,!1,52,8)},j.prototype.writeUIntLE=function(t,e,n,r){(t=+t,e|=0,n|=0,r)||ht(this,t,e,n,Math.pow(2,8*n)-1,0);var i=1,o=0;for(this[e]=255&t;++o<n&&(i*=256);)this[e+o]=t/i&255;return e+n},j.prototype.writeUIntBE=function(t,e,n,r){(t=+t,e|=0,n|=0,r)||ht(this,t,e,n,Math.pow(2,8*n)-1,0);var i=n-1,o=1;for(this[e+i]=255&t;--i>=0&&(o*=256);)this[e+i]=t/o&255;return e+n},j.prototype.writeUInt8=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,1,255,0),j.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),this[e]=255&t,e+1},j.prototype.writeUInt16LE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,2,65535,0),j.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):lt(this,t,e,!0),e+2},j.prototype.writeUInt16BE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,2,65535,0),j.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):lt(this,t,e,!1),e+2},j.prototype.writeUInt32LE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,4,4294967295,0),j.TYPED_ARRAY_SUPPORT?(this[e+3]=t>>>24,this[e+2]=t>>>16,this[e+1]=t>>>8,this[e]=255&t):ft(this,t,e,!0),e+4},j.prototype.writeUInt32BE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,4,4294967295,0),j.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):ft(this,t,e,!1),e+4},j.prototype.writeIntLE=function(t,e,n,r){if(t=+t,e|=0,!r){var i=Math.pow(2,8*n-1);ht(this,t,e,n,i-1,-i)}var o=0,s=1,a=0;for(this[e]=255&t;++o<n&&(s*=256);)t<0&&0===a&&0!==this[e+o-1]&&(a=1),this[e+o]=(t/s|0)-a&255;return e+n},j.prototype.writeIntBE=function(t,e,n,r){if(t=+t,e|=0,!r){var i=Math.pow(2,8*n-1);ht(this,t,e,n,i-1,-i)}var o=n-1,s=1,a=0;for(this[e+o]=255&t;--o>=0&&(s*=256);)t<0&&0===a&&0!==this[e+o+1]&&(a=1),this[e+o]=(t/s|0)-a&255;return e+n},j.prototype.writeInt8=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,1,127,-128),j.TYPED_ARRAY_SUPPORT||(t=Math.floor(t)),t<0&&(t=255+t+1),this[e]=255&t,e+1},j.prototype.writeInt16LE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,2,32767,-32768),j.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8):lt(this,t,e,!0),e+2},j.prototype.writeInt16BE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,2,32767,-32768),j.TYPED_ARRAY_SUPPORT?(this[e]=t>>>8,this[e+1]=255&t):lt(this,t,e,!1),e+2},j.prototype.writeInt32LE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,4,2147483647,-2147483648),j.TYPED_ARRAY_SUPPORT?(this[e]=255&t,this[e+1]=t>>>8,this[e+2]=t>>>16,this[e+3]=t>>>24):ft(this,t,e,!0),e+4},j.prototype.writeInt32BE=function(t,e,n){return t=+t,e|=0,n||ht(this,t,e,4,2147483647,-2147483648),t<0&&(t=4294967295+t+1),j.TYPED_ARRAY_SUPPORT?(this[e]=t>>>24,this[e+1]=t>>>16,this[e+2]=t>>>8,this[e+3]=255&t):ft(this,t,e,!1),e+4},j.prototype.writeFloatLE=function(t,e,n){return dt(this,t,e,!0,n)},j.prototype.writeFloatBE=function(t,e,n){return dt(this,t,e,!1,n)},j.prototype.writeDoubleLE=function(t,e,n){return gt(this,t,e,!0,n)},j.prototype.writeDoubleBE=function(t,e,n){return gt(this,t,e,!1,n)},j.prototype.copy=function(t,e,n,r){if(n||(n=0),r||0===r||(r=this.length),e>=t.length&&(e=t.length),e||(e=0),r>0&&r<n&&(r=n),r===n)return 0;if(0===t.length||0===this.length)return 0;if(e<0)throw new RangeError("targetStart out of bounds");if(n<0||n>=this.length)throw new RangeError("sourceStart out of bounds");if(r<0)throw new RangeError("sourceEnd out of bounds");r>this.length&&(r=this.length),t.length-e<r-n&&(r=t.length-e+n);var i,o=r-n;if(this===t&&n<e&&e<r)for(i=o-1;i>=0;--i)t[i+e]=this[i+n];else if(o<1e3||!j.TYPED_ARRAY_SUPPORT)for(i=0;i<o;++i)t[i+e]=this[i+n];else Uint8Array.prototype.set.call(t,this.subarray(n,n+o),e);return o},j.prototype.fill=function(t,e,n,r){if("string"==typeof t){if("string"==typeof e?(r=e,e=0,n=this.length):"string"==typeof n&&(r=n,n=this.length),1===t.length){var i=t.charCodeAt(0);i<256&&(t=i)}if(void 0!==r&&"string"!=typeof r)throw new TypeError("encoding must be a string");if("string"==typeof r&&!j.isEncoding(r))throw new TypeError("Unknown encoding: "+r)}else"number"==typeof t&&(t&=255);if(e<0||this.length<e||this.length<n)throw new RangeError("Out of range index");if(n<=e)return this;var o;if(e>>>=0,n=void 0===n?this.length:n>>>0,t||(t=0),"number"==typeof t)for(o=e;o<n;++o)this[o]=t;else{var s=F(t)?t:_t(new j(t,r).toString()),a=s.length;for(o=0;o<n-e;++o)this[o+e]=s[o%a]}return this};var wt=/[^+\/0-9A-Za-z-_]/g;function mt(t){return t<16?"0"+t.toString(16):t.toString(16)}function _t(t,e){var n;e=e||1/0;for(var r=t.length,i=null,o=[],s=0;s<r;++s){if((n=t.charCodeAt(s))>55295&&n<57344){if(!i){if(n>56319){(e-=3)>-1&&o.push(239,191,189);continue}if(s+1===r){(e-=3)>-1&&o.push(239,191,189);continue}i=n;continue}if(n<56320){(e-=3)>-1&&o.push(239,191,189),i=n;continue}n=65536+(i-55296<<10|n-56320)}else i&&(e-=3)>-1&&o.push(239,191,189);if(i=null,n<128){if((e-=1)<0)break;o.push(n)}else if(n<2048){if((e-=2)<0)break;o.push(n>>6|192,63&n|128)}else if(n<65536){if((e-=3)<0)break;o.push(n>>12|224,n>>6&63|128,63&n|128)}else{if(!(n<1114112))throw new Error("Invalid code point");if((e-=4)<0)break;o.push(n>>18|240,n>>12&63|128,n>>6&63|128,63&n|128)}}return o}function yt(t){return function(t){var e,n,r,i,o,s;x||k();var a=t.length;if(a%4>0)throw new Error("Invalid string. Length must be a multiple of 4");o="="===t[a-2]?2:"="===t[a-1]?1:0,s=new R(3*a/4-o),r=o>0?a-4:a;var u=0;for(e=0,n=0;e<r;e+=4,n+=3)i=P[t.charCodeAt(e)]<<18|P[t.charCodeAt(e+1)]<<12|P[t.charCodeAt(e+2)]<<6|P[t.charCodeAt(e+3)],s[u++]=i>>16&255,s[u++]=i>>8&255,s[u++]=255&i;return 2===o?(i=P[t.charCodeAt(e)]<<2|P[t.charCodeAt(e+1)]>>4,s[u++]=255&i):1===o&&(i=P[t.charCodeAt(e)]<<10|P[t.charCodeAt(e+1)]<<4|P[t.charCodeAt(e+2)]>>2,s[u++]=i>>8&255,s[u++]=255&i),s}(function(t){if((t=function(t){return t.trim?t.trim():t.replace(/^\s+|\s+$/g,"")}(t).replace(wt,"")).length<2)return"";for(;t.length%4!=0;)t+="=";return t}(t))}function vt(t,e,n,r){for(var i=0;i<r&&!(i+n>=e.length||i>=t.length);++i)e[i+n]=t[i];return i}function bt(t){return!!t.constructor&&"function"==typeof t.constructor.isBuffer&&t.constructor.isBuffer(t)}var Et,St=Object.defineProperty,At=(t,e,n)=>(((t,e,n)=>{e in t?St(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n})(t,"symbol"!=typeof e?e+"":e,n),n),Tt=(t,e,n)=>{if(!e.has(t))throw TypeError("Cannot "+n)},Pt=(t,e,n)=>(Tt(t,e,"read from private field"),n?n.call(t):e.get(t)),Rt=new Intl.Collator(0,{numeric:1}).compare;function xt(t,e,n){return t=t.split("."),e=e.split("."),Rt(t[0],e[0])||Rt(t[1],e[1])||(e[2]=e.slice(2).join("."),(n=/[.-]/.test(t[2]=t.slice(2).join(".")))==/[.-]/.test(e[2])?Rt(t[2],e[2]):n?-1:1)}const kt="queue/data",$t="upload",Ut="This application is currently busy. Please try again. ",Ot="Connection errored out. ",Bt="Could not resolve app config. ",Ct="Space metadata could not be loaded. ",Dt="Invalid credentials. Could not login. ",It="Root URL not found in client config";function Nt(t,e,n){return e.startsWith("http://")||e.startsWith("https://")?n?t:e:t+e}async function jt(t,e,n){try{const r=await fetch(`https://huggingface.co/api/spaces/${t}/jwt`,{headers:{Authorization:`Bearer ${e}`,...n?{Cookie:n}:{}}});return(await r.json()).token||!1}catch(t){return!1}}async function Lt(t){var e;const n=this.options.hf_token?{Authorization:`Bearer ${this.options.hf_token}`}:{};if(n["Content-Type"]="application/json","undefined"!=typeof window&&window.gradio_config&&"http://localhost:9876"!==location.origin&&!window.gradio_config.dev_mode){const e=window.gradio_config.root,n=window.gradio_config;let r=Nt(t,n.root,!1);return n.root=r,{...n,path:e}}if(t){const r=Gt(t,"config"),i=await this.fetch(r,{headers:n,credentials:"include"});if(401===(null==i?void 0:i.status)&&!this.options.auth)throw new Error("Login credentials are required to access this space.");if(401===(null==i?void 0:i.status)&&this.options.auth)throw new Error(Dt);if(200===(null==i?void 0:i.status)){let n=await i.json();return n.path=n.path??"",n.root=t,null==(e=n.dependencies)||e.forEach(((t,e)=>{void 0===t.id&&(t.id=e)})),n}if(401===(null==i?void 0:i.status))throw new Error("Not authorized to access this space. ");throw new Error(Bt)}throw new Error(Bt)}async function zt(){const{http_protocol:t,host:e}=await Jt(this.app_reference,this.options.hf_token);try{if(this.options.auth){const n=await qt(t,e,this.options.auth,this.fetch,this.options.hf_token);n&&this.set_cookies(n)}}catch(t){throw Error(t.message)}}async function qt(t,e,n,r,i){const o=new FormData;o.append("username",null==n?void 0:n[0]),o.append("password",null==n?void 0:n[1]);let s={};i&&(s.Authorization=`Bearer ${i}`);const a=await r(`${t}//${e}/login`,{headers:s,method:"POST",body:o,credentials:"include"});if(200===a.status)return a.headers.get("set-cookie");throw 401===a.status?new Error(Dt):new Error(Ct)}function Yt(t){if(t.startsWith("http")){const{protocol:e,host:n,pathname:r}=new URL(t);return{ws_protocol:"https:"===e?"wss":"ws",http_protocol:e,host:n+("/"!==r?r:"")}}return t.startsWith("file:")?{ws_protocol:"ws",http_protocol:"http:",host:"lite.local"}:{ws_protocol:"wss",http_protocol:"https:",host:new URL(t).host}}const Mt=t=>{let e=[];return t.split(/,(?=\s*[^\s=;]+=[^\s=;]+)/).forEach((t=>{const[n,r]=t.split(";")[0].split("=");n&&r&&e.push(`${n.trim()}=${r.trim()}`)})),e},Ft=/^[a-zA-Z0-9_\-\.]+\/[a-zA-Z0-9_\-\.]+$/,Wt=/.*hf\.space\/{0,1}.*$/;async function Jt(t,e){const n={};e&&(n.Authorization=`Bearer ${e}`);const r=t.trim().replace(/\/$/,"");if(Ft.test(r))try{const e=await fetch(`https://huggingface.co/api/spaces/${r}/host`,{headers:n});return{space_id:t,...Yt((await e.json()).host)}}catch(t){throw new Error(Ct)}if(Wt.test(r)){const{ws_protocol:t,http_protocol:e,host:n}=Yt(r);return{space_id:n.split("/")[0].replace(".hf.space",""),ws_protocol:t,http_protocol:e,host:n}}return{space_id:!1,...Yt(r)}}const Gt=(...t)=>{try{return t.reduce(((t,e)=>(t=t.replace(/\/+$/,""),e=e.replace(/^\/+/,""),new URL(e,t+"/").toString())))}catch(t){throw new Error("Invalid URL. A full URL path is required.")}};function Ht(t,e,n,r){if("Api"===e)return t.type;switch(null==t?void 0:t.type){case"string":return"string";case"boolean":return"boolean";case"number":return"number"}return"JSONSerializable"===n||"StringSerializable"===n?"any":"ListStringSerializable"===n?"string[]":"Image"===e?"parameter"===r?"Blob | File | Buffer":"string":"FileSerializable"===n?"array"===(null==t?void 0:t.type)?"parameter"===r?"(Blob | File | Buffer)[]":"{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}[]":"parameter"===r?"Blob | File | Buffer":"{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}":"GallerySerializable"===n?"parameter"===r?"[(Blob | File | Buffer), (string | null)][]":"[{ name: string; data: string; size?: number; is_file?: boolean; orig_name?: string}, (string | null))][]":void 0}function Vt(t,e){return"GallerySerializable"===e?"array of [file, label] tuples":"ListStringSerializable"===e?"array of strings":"FileSerializable"===e?"array of files or single file":null==t?void 0:t.description}function Zt(t,e){const n=!0;switch(t.msg){case"send_data":return{type:"data"};case"send_hash":return{type:"hash"};case"queue_full":return{type:"update",status:{queue:n,message:Ut,stage:"error",code:t.code,success:t.success}};case"heartbeat":return{type:"heartbeat"};case"unexpected_error":return{type:"unexpected_error",status:{queue:n,message:t.message,stage:"error",success:!1}};case"estimation":return{type:"update",status:{queue:n,stage:e||"pending",code:t.code,size:t.queue_size,position:t.rank,eta:t.rank_eta,success:t.success}};case"progress":return{type:"update",status:{queue:n,stage:"pending",code:t.code,progress_data:t.progress_data,success:t.success}};case"log":return{type:"log",data:t};case"process_generating":return{type:"generating",status:{queue:n,message:t.success?null:t.output.error,stage:t.success?"generating":"error",code:t.code,progress_data:t.progress_data,eta:t.average_duration,changed_state_ids:t.success?t.output.changed_state_ids:void 0},data:t.success?t.output:null};case"process_streaming":return{type:"streaming",status:{queue:n,message:t.output.error,stage:"streaming",time_limit:t.time_limit,code:t.code,progress_data:t.progress_data,eta:t.eta},data:t.output};case"process_completed":return"error"in t.output?{type:"update",status:{queue:n,title:t.output.title,message:t.output.error,visible:t.output.visible,duration:t.output.duration,stage:"error",code:t.code,success:t.success}}:{type:"complete",status:{queue:n,message:t.success?void 0:t.output.error,stage:t.success?"complete":"error",code:t.code,progress_data:t.progress_data,changed_state_ids:t.success?t.output.changed_state_ids:void 0},data:t.success?t.output:null};case"process_starts":return{type:"update",status:{queue:n,stage:"pending",code:t.code,size:t.rank,position:0,success:t.success,eta:t.eta},original_msg:"process_starts"}}return{type:"none",status:{stage:"error",queue:n}}}async function Kt(){if(this.api_info)return this.api_info;const{hf_token:t}=this.options,{config:e}=this,n={"Content-Type":"application/json"};if(t&&(n.Authorization=`Bearer ${t}`),e)try{let t,r;if("undefined"!=typeof window&&window.gradio_api_info)r=window.gradio_api_info;else{if(xt((null==e?void 0:e.version)||"2.0.0","3.30")<0)t=await this.fetch("https://gradio-space-api-fetcher-v2.hf.space/api",{method:"POST",body:JSON.stringify({serialize:!1,config:JSON.stringify(e)}),headers:n,credentials:"include"});else{const r=Gt(e.root,this.api_prefix,"info");t=await this.fetch(r,{headers:n,credentials:"include"})}if(!t.ok)throw new Error(Ot);r=await t.json()}return"api"in r&&(r=r.api),r.named_endpoints["/predict"]&&!r.unnamed_endpoints[0]&&(r.unnamed_endpoints[0]=r.named_endpoints["/predict"]),function(t,e,n){const r={named_endpoints:{},unnamed_endpoints:{}};return Object.keys(t).forEach((i=>{"named_endpoints"!==i&&"unnamed_endpoints"!==i||(r[i]={},Object.entries(t[i]).forEach((([t,{parameters:o,returns:s}])=>{var a,u,c,h;const l=(null==(a=e.dependencies.find((e=>e.api_name===t||e.api_name===t.replace("/",""))))?void 0:a.id)||n[t.replace("/","")]||-1,f=-1!==l?null==(u=e.dependencies.find((t=>t.id==l)))?void 0:u.types:{generator:!1,cancel:!1};if(-1!==l&&(null==(h=null==(c=e.dependencies.find((t=>t.id==l)))?void 0:c.inputs)?void 0:h.length)!==o.length){const t=e.dependencies.find((t=>t.id==l)).inputs.map((t=>{var n;return null==(n=e.components.find((e=>e.id===t)))?void 0:n.type}));try{t.forEach(((t,e)=>{if("state"===t){const t={component:"state",example:null,parameter_default:null,parameter_has_default:!0,parameter_name:null,hidden:!0};o.splice(e,0,t)}}))}catch(t){console.error(t)}}const p=(t,e,n,r)=>({...t,description:Vt(null==t?void 0:t.type,n),type:Ht(null==t?void 0:t.type,e,n,r)||""});r[i][t]={parameters:o.map((t=>p(t,null==t?void 0:t.component,null==t?void 0:t.serializer,"parameter"))),returns:s.map((t=>p(t,null==t?void 0:t.component,null==t?void 0:t.serializer,"return"))),type:f}})))})),r}(r,e,this.api_map)}catch(t){t.message}}async function Qt(t,e,n){var r;const i={};(null==(r=null==this?void 0:this.options)?void 0:r.hf_token)&&(i.Authorization=`Bearer ${this.options.hf_token}`);const o=[];let s;for(let r=0;r<e.length;r+=1e3){const a=e.slice(r,r+1e3),u=new FormData;a.forEach((t=>{u.append("files",t)}));try{const e=n?`${t}${this.api_prefix}/${$t}?upload_id=${n}`:`${t}${this.api_prefix}/${$t}`;s=await this.fetch(e,{method:"POST",body:u,headers:i,credentials:"include"})}catch(t){throw new Error(Ot+t.message)}if(!s.ok){const t=await s.text();return{error:`HTTP ${s.status}: ${t}`}}const c=await s.json();c&&o.push(...c)}return{files:o}}async function Xt(t,e,n,r){let i=(Array.isArray(t)?t:[t]).map((t=>t.blob));const o=i.filter((t=>t.size>(r??1/0)));if(o.length)throw new Error(`File size exceeds the maximum allowed size of ${r} bytes: ${o.map((t=>t.name)).join(", ")}`);return await Promise.all(await this.upload_files(e,i,n).then((async n=>{if(n.error)throw new Error(n.error);return n.files?n.files.map(((n,r)=>new ee({...t[r],path:n,url:`${e}${this.api_prefix}/file=${n}`}))):[]})))}async function te(t,e){return t.map((t=>new ee({path:t.name,orig_name:t.name,blob:t,size:t.size,mime_type:t.type,is_stream:e})))}class ee{constructor({path:t,url:e,orig_name:n,size:r,blob:i,is_stream:o,mime_type:s,alt_text:a,b64:u}){At(this,"path"),At(this,"url"),At(this,"orig_name"),At(this,"size"),At(this,"blob"),At(this,"is_stream"),At(this,"mime_type"),At(this,"alt_text"),At(this,"b64"),At(this,"meta",{_type:"gradio.FileData"}),this.path=t,this.url=e,this.orig_name=n,this.size=r,this.blob=e?void 0:i,this.is_stream=o,this.mime_type=s,this.alt_text=a,this.b64=u}}class ne{constructor(t,e){At(this,"type"),At(this,"command"),At(this,"meta"),At(this,"fileData"),this.type="command",this.command=t,this.meta=e}}const re=void 0!==A&&A.versions&&A.versions.node;function ie(t,e,n){for(;n.length>1;){const e=n.shift();if("string"!=typeof e&&"number"!=typeof e)throw new Error("Invalid key type");t=t[e]}const r=n.shift();if("string"!=typeof r&&"number"!=typeof r)throw new Error("Invalid key type");t[r]=e}async function oe(t,e=void 0,n=[],r=!1,i=void 0){if(Array.isArray(t)){let o=[];return await Promise.all(t.map((async(s,a)=>{var u;let c=n.slice();c.push(String(a));const h=await oe(t[a],r?(null==(u=null==i?void 0:i.parameters[a])?void 0:u.component)||void 0:e,c,!1,i);o=o.concat(h)}))),o}if(globalThis.Buffer&&t instanceof globalThis.Buffer||t instanceof Blob)return[{path:n,blob:new Blob([t]),type:e}];if("object"==typeof t&&null!==t){let e=[];for(const r of Object.keys(t)){const o=[...n,r],s=t[r];e=e.concat(await oe(s,void 0,o,!1,i))}return e}return[]}function se(t){if("string"==typeof t){if(t.startsWith("http://")||t.startsWith("https://"))return{path:t,url:t,orig_name:t.split("/").pop()??"unknown",meta:{_type:"gradio.FileData"}};if(re)return new ne("upload_file",{path:t,name:t,orig_path:t})}else{if("undefined"!=typeof File&&t instanceof File)return new Blob([t]);if(t instanceof j)return new Blob([t]);if(t instanceof Blob)return t}throw new Error("Invalid input: must be a URL, File, Blob, or Buffer object.")}function ae(t,e,n,r,i=!1){if("input"===r&&!i)throw new Error("Invalid code path. Cannot skip state inputs for input.");if("output"===r&&i)return t;let o=[],s=0;const a="input"===r?e.inputs:e.outputs;for(let e=0;e<a.length;e++){const r=a[e],u=n.find((t=>t.id===r));if("state"!==(null==u?void 0:u.type)){const e=t[s];o.push(e),s++}else{if(!i){s++;continue}if(t.length===a.length){const e=t[s];o.push(e),s++}else o.push(null)}}return o}async function ue(t,e,n){const r=this;await async function(t,e){var n,r;if(!(null==(n=t.config)?void 0:n.root)&&!(null==(r=t.config)?void 0:r.root_url))throw new Error(It);await ce(t,e)}(r,e);const i=await oe(e,void 0,[],!0,n);return(await Promise.all(i.map((async({path:e,blob:n,type:i})=>{if(!n)return{path:e,type:i};const o=await r.upload_files(t,[n]);return{path:e,file_url:o.files&&o.files[0],type:i,name:"undefined"!=typeof File&&n instanceof File?null==n?void 0:n.name:void 0}})))).forEach((({path:t,file_url:n,type:r,name:i})=>{if("Gallery"===r)ie(e,n,t);else if(n){const r=new ee({path:n,orig_name:i});ie(e,r,t)}})),e}async function ce(t,e,n=[]){for(const r in e)e[r]instanceof ne?await he(t,e,r):"object"==typeof e[r]&&null!==e[r]&&await ce(t,e[r],[...n,r])}async function he(t,e,n){var r,i;let o=e[n];const s=(null==(r=t.config)?void 0:r.root)||(null==(i=t.config)?void 0:i.root_url);if(!s)throw new Error(It);try{let r,i;if(void 0===A||!A.versions||!A.versions.node)throw new Error("File system access is only available in Node.js environments");{const t=await import("/npm/fs/promises/+esm");i=(await import("/npm/path/+esm")).resolve(A.cwd(),o.meta.path),r=await t.readFile(i)}const a=new Blob([r],{type:"application/octet-stream"}),u=await t.upload_files(s,[a]),c=u.files&&u.files[0];if(c){const t=new ee({path:c,orig_name:o.meta.name||""});e[n]=t}}catch(t){console.error("Error uploading file",t)}}async function le(t,e,n){const r={"Content-Type":"application/json"};this.options.hf_token&&(r.Authorization=`Bearer ${this.options.hf_token}`);try{var i=await this.fetch(t,{method:"POST",body:JSON.stringify(e),headers:{...r,...n},credentials:"include"})}catch(t){return[{error:Ot},500]}let o,s;try{o=await i.json(),s=i.status}catch(t){o={error:`Could not parse server response: ${t}`},s=500}return[o,s]}async function fe(t,e={}){let n=!1,r=!1;if(!this.config)throw new Error("Could not resolve app config");if("number"==typeof t)this.config.dependencies.find((e=>e.id==t));else{const e=t.replace(/^\//,"");this.config.dependencies.find((t=>t.id==this.api_map[e]))}return new Promise((async(i,o)=>{const s=this.submit(t,e,null,null,!0);let a;for await(const t of s)"data"===t.type&&(r&&i(a),n=!0,a=t),"status"===t.type&&("error"===t.stage&&o(t),"complete"===t.stage&&(r=!0,n&&i(a)))}))}async function pe(t,e,n){let r,i,o="subdomain"===e?`https://huggingface.co/api/spaces/by-subdomain/${t}`:`https://huggingface.co/api/spaces/${t}`;try{if(r=await fetch(o),i=r.status,200!==i)throw new Error;r=await r.json()}catch(t){return void n({status:"error",load_status:"error",message:"Could not get space status. ",detail:"NOT_FOUND"})}if(!r||200!==i)return;const{runtime:{stage:s},id:a}=r;switch(s){case"STOPPED":case"SLEEPING":n({status:"sleeping",load_status:"pending",message:"Space is asleep. Waking it up...",detail:s}),setTimeout((()=>{pe(t,e,n)}),1e3);break;case"PAUSED":n({status:"paused",load_status:"error",message:"This space has been paused by the author. If you would like to try this demo, consider duplicating the space.",detail:s,discussions_enabled:await we(a)});break;case"RUNNING":case"RUNNING_BUILDING":n({status:"running",load_status:"complete",message:"Space is running.",detail:s});break;case"BUILDING":n({status:"building",load_status:"pending",message:"Space is building...",detail:s}),setTimeout((()=>{pe(t,e,n)}),1e3);break;case"APP_STARTING":n({status:"starting",load_status:"pending",message:"Space is starting...",detail:s}),setTimeout((()=>{pe(t,e,n)}),1e3);break;default:n({status:"space_error",load_status:"error",message:"This space is experiencing an issue.",detail:s,discussions_enabled:await we(a)})}}const de=async(t,e)=>{let n=0;return new Promise((r=>{pe(t,Ft.test(t)?"space_name":"subdomain",(i=>{e(i),"running"===i.status||"error"===i.status||"paused"===i.status||"space_error"===i.status?r():"sleeping"!==i.status&&"building"!==i.status||(n<12?(n++,setTimeout((()=>{de(t,e).then(r)}),5e3)):r())}))}))},ge=/^(?=[^]*\b[dD]iscussions{0,1}\b)(?=[^]*\b[dD]isabled\b)[^]*$/;async function we(t){try{const e=await fetch(`https://huggingface.co/api/spaces/${t}/discussions`,{method:"HEAD"}),n=e.headers.get("x-error-message");return!(!e.ok||n&&ge.test(n))}catch(t){return!1}}const me=["cpu-basic","cpu-upgrade","cpu-xl","t4-small","t4-medium","a10g-small","a10g-large","a10g-largex2","a10g-largex4","a100-large","zero-a10g","h100","h100x8"];async function _e(t,e){const{hf_token:n,private:r,hardware:i,timeout:o,auth:s}=e;if(i&&!me.includes(i))throw new Error(`Invalid hardware type provided. Valid types are: ${me.map((t=>`"${t}"`)).join(",")}.`);const{http_protocol:a,host:u}=await Jt(t,n);let c=null;if(s){const t=await qt(a,u,s,fetch);t&&(c=Mt(t))}const h={Authorization:`Bearer ${n}`,"Content-Type":"application/json",...c?{Cookie:c.join("; ")}:{}},l=(await(await fetch("https://huggingface.co/api/whoami-v2",{headers:h})).json()).name,f=t.split("/")[1],p={repository:`${l}/${f}`};let d;r&&(p.private=!0);try{i||(d=await async function(t,e){const n={};e&&(n.Authorization=`Bearer ${e}`);try{const e=await fetch(`https://huggingface.co/api/spaces/${t}/runtime`,{headers:n});if(200!==e.status)throw new Error("Space hardware could not be obtained.");const{hardware:r}=await e.json();return r.current}catch(t){throw new Error(t.message)}}(t,n))}catch(t){throw Error(Ct+t.message)}const g=i||d||"cpu-basic";p.hardware=g;try{const r=await fetch(`https://huggingface.co/api/spaces/${t}/duplicate`,{method:"POST",headers:h,body:JSON.stringify(p)});if(409===r.status)try{return await xe.connect(`${l}/${f}`,e)}catch(t){throw console.error("Failed to connect Client instance:",t),t}else if(200!==r.status)throw new Error(r.statusText);const i=await r.json();return await async function(t,e,n){const r={};n&&(r.Authorization=`Bearer ${n}`);const i={seconds:e};try{const e=await fetch(`https://huggingface.co/api/spaces/${t}/sleeptime`,{method:"POST",headers:{"Content-Type":"application/json",...r},body:JSON.stringify(i)});if(200!==e.status)throw new Error("Could not set sleep timeout on duplicated Space. Please visit *ADD HF LINK TO SETTINGS* to set a timeout manually to reduce billing charges.");return await e.json()}catch(t){throw new Error(t.message)}}(`${l}/${f}`,o||300,n),await xe.connect(function(t){const e=/https:\/\/huggingface.co\/spaces\/([^/]+\/[^/]+)/,n=t.match(e);if(n)return n[1]}(i.url),e)}catch(t){throw new Error(t)}}class ye extends TransformStream{constructor(t={allowCR:!1}){super({transform:(e,n)=>{for(e=Pt(this,Et)+e;;){const r=e.indexOf("\n"),i=t.allowCR?e.indexOf("\r"):-1;if(-1!==i&&i!==e.length-1&&(-1===r||r-1>i)){n.enqueue(e.slice(0,i)),e=e.slice(i+1);continue}if(-1===r)break;const o="\r"===e[r-1]?r-1:r;n.enqueue(e.slice(0,o)),e=e.slice(r+1)}var r,i,o,s;o=e,Tt(r=this,i=Et,"write to private field"),s?s.call(r,o):i.set(r,o)},flush:e=>{if(""===Pt(this,Et))return;const n=t.allowCR&&Pt(this,Et).endsWith("\r")?Pt(this,Et).slice(0,-1):Pt(this,Et);e.enqueue(n)}}),((t,e,n)=>{if(e.has(t))throw TypeError("Cannot add the same private member more than once");e instanceof WeakSet?e.add(t):e.set(t,n)})(this,Et,"")}}function ve(t){let e=/[:]\s*/.exec(t),n=e&&e.index;if(n)return[t.substring(0,n),t.substring(n+e[0].length)]}function be(t,e,n){t.get(e)||t.set(e,n)}async function*Ee(t,e){if(!t.body)return;let n,r,i=function(t){let e=new TextDecoderStream,n=new ye({allowCR:!0});return t.pipeThrough(e).pipeThrough(n)}(t.body).getReader();for(;;){if(e&&e.aborted)return i.cancel();if(n=await i.read(),n.done)return;if(!n.value){r&&(yield r),r=void 0;continue}let[t,o]=ve(n.value)||[];t&&("data"===t?(r||(r={}),r[t]=r[t]?r[t]+"\n"+o:o):"event"===t?(r||(r={}),r[t]=o):"id"===t?(r||(r={}),r[t]=+o||o):"retry"===t&&(r||(r={}),r[t]=+o||void 0))}}async function Se(){let{event_callbacks:t,unclosed_events:e,pending_stream_messages:n,stream_status:r,config:i,jwt:o}=this;const s=this;if(!i)throw new Error("Could not resolve app config");r.open=!0;let a=null,u=new URLSearchParams({session_hash:this.session_hash}).toString(),c=new URL(`${i.root}${this.api_prefix}/${kt}?${u}`);o&&c.searchParams.set("__sign",o),a=this.stream(c),a?(a.onmessage=async function(o){let a=JSON.parse(o.data);if("close_stream"===a.msg)return void Ae(r,s.abort_controller);const u=a.event_id;if(u)if(t[u]&&i){"process_completed"===a.msg&&["sse","sse_v1","sse_v2","sse_v2.1","sse_v3"].includes(i.protocol)&&e.delete(u);let n=t[u];"undefined"!=typeof window&&"undefined"!=typeof document?setTimeout(n,0,a):n(a)}else n[u]||(n[u]=[]),n[u].push(a);else await Promise.all(Object.keys(t).map((e=>t[e](a))))},a.onerror=async function(){await Promise.all(Object.keys(t).map((e=>t[e]({msg:"unexpected_error",message:Ot}))))}):console.warn("Cannot connect to SSE endpoint: "+c.toString())}function Ae(t,e){t&&(t.open=!1,null==e||e.abort())}function Te(t,e,n){!t[e]?(t[e]=[],n.data.forEach(((n,r)=>{t[e][r]=n}))):n.data.forEach(((r,i)=>{let o=(s=t[e][i],r.forEach((([t,e,n])=>{s=function(t,e,n,r){if(0===e.length){if("replace"===n)return r;if("append"===n)return t+r;throw new Error(`Unsupported action: ${n}`)}let i=t;for(let t=0;t<e.length-1;t++)i=i[e[t]];const o=e[e.length-1];switch(n){case"replace":i[o]=r;break;case"append":i[o]+=r;break;case"add":Array.isArray(i)?i.splice(Number(o),0,r):i[o]=r;break;case"delete":Array.isArray(i)?i.splice(Number(o),1):delete i[o];break;default:throw new Error(`Unknown action: ${n}`)}return t}(s,e,t,n)})),s);var s;t[e][i]=o,n.data[i]=o}))}function Pe(t,e={}){const n={close:()=>{console.warn("Method not implemented.")},onerror:null,onmessage:null,onopen:null,readyState:0,url:t.toString(),withCredentials:!1,CONNECTING:0,OPEN:1,CLOSED:2,addEventListener:()=>{throw new Error("Method not implemented.")},dispatchEvent:()=>{throw new Error("Method not implemented.")},removeEventListener:()=>{throw new Error("Method not implemented.")}};return async function(t,e){let n=new Request(t,e);be(n.headers,"Accept","text/event-stream"),be(n.headers,"Content-Type","application/json");let r=await fetch(n);if(!r.ok)throw r;return Ee(r,n.signal)}(t,e).then((async t=>{n.readyState=n.OPEN;try{for await(const e of t)n.onmessage&&n.onmessage(e);n.readyState=n.CLOSED}catch(t){n.onerror&&n.onerror(t),n.readyState=n.CLOSED}})).catch((t=>{console.error(t),n.onerror&&n.onerror(t),n.readyState=n.CLOSED})),n}function Re(t,e={},n,r,i){var o;try{let s=function(t){(i||M[t.type])&&h(t)},a=function(){for(G=!0;V.length>0;)V.shift()({value:void 0,done:!0})},u=function(t){G||(V.length>0?V.shift()(t):H.push(t))},c=function(t){u(function(t){return{then:(e,n)=>n(t)}}(t)),a()},h=function(t){u({value:t,done:!1})},l=function(){return H.length>0?Promise.resolve(H.shift()):G?Promise.resolve({value:void 0,done:!0}):new Promise((t=>V.push(t)))};const{hf_token:f}=this.options,{fetch:p,app_reference:d,config:g,session_hash:w,api_info:m,api_map:_,stream_status:y,pending_stream_messages:v,pending_diff_streams:b,event_callbacks:E,unclosed_events:S,post_data:A,options:T,api_prefix:P}=this,R=this;if(!m)throw new Error("No API found");if(!g)throw new Error("Could not resolve app config");let x,k,{fn_index:$,endpoint_info:U,dependency:O}=function(t,e,n,r){let i,o,s;if("number"==typeof e)i=e,o=t.unnamed_endpoints[i],s=r.dependencies.find((t=>t.id==e));else{const a=e.replace(/^\//,"");i=n[a],o=t.named_endpoints[e.trim()],s=r.dependencies.find((t=>t.id==n[a]))}if("number"!=typeof i)throw new Error("There is no endpoint matching that name of fn_index matching that number.");return{fn_index:i,endpoint_info:o,dependency:s}}(m,t,_,g),B=((t=[],e)=>{const n=e?e.parameters:[];if(Array.isArray(t))return t.length>n.length&&console.warn("Too many arguments provided for the endpoint."),t;const r=[],i=Object.keys(t);return n.forEach(((e,n)=>{if(t.hasOwnProperty(e.parameter_name))r[n]=t[e.parameter_name];else{if(!e.parameter_has_default)throw new Error(`No value provided for required parameter: ${e.parameter_name}`);r[n]=e.parameter_default}})),i.forEach((t=>{if(!n.some((e=>e.parameter_name===t)))throw new Error(`Parameter \`${t}\` is not a valid keyword argument. Please refer to the API for usage.`)})),r.forEach(((t,e)=>{if(void 0===t&&!n[e].parameter_has_default)throw new Error(`No value provided for required parameter: ${n[e].parameter_name}`)})),r})(e,U),C=g.protocol??"ws",D="",I=()=>D;const N="number"==typeof t?"/predict":t;let j,L=null,z=!1,q={},Y="undefined"!=typeof window&&"undefined"!=typeof document?new URLSearchParams(window.location.search).toString():"";const M=(null==(o=null==T?void 0:T.events)?void 0:o.reduce(((t,e)=>(t[e]=!0,t)),{}))||{};async function F(){const t={stage:"complete",queue:!1,time:new Date};z=t,s({...t,type:"status",endpoint:N,fn_index:$});let e={},n={};"ws"===C?(x&&0===x.readyState?x.addEventListener("open",(()=>{x.close()})):x.close(),e={fn_index:$,session_hash:w}):(Ae(y,R.abort_controller),a(),e={event_id:L},n={event_id:L,session_hash:w,fn_index:$});try{if(!g)throw new Error("Could not resolve app config");"event_id"in n&&await p(`${g.root}${P}/cancel`,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(n)}),await p(`${g.root}${P}/reset`,{headers:{"Content-Type":"application/json"},method:"POST",body:JSON.stringify(e)})}catch(t){console.warn("The `/reset` endpoint could not be called. Subsequent endpoint results may be unreliable.")}}const W=async t=>{await this._resolve_hearbeat(t)};async function J(t){if(!g)return;let e=t.render_id;g.components=[...g.components.filter((t=>t.props.rendered_in!==e)),...t.components],g.dependencies=[...g.dependencies.filter((t=>t.rendered_in!==e)),...t.dependencies];const n=g.components.some((t=>"state"===t.type)),r=g.dependencies.some((t=>t.targets.some((t=>"unload"===t[1]))));g.connect_heartbeat=n||r,await W(g),s({type:"render",data:t,endpoint:N,fn_index:$})}this.handle_blob(g.root,B,U).then((async t=>{var e;let i=ae(t,O,g.components,"input",!0);if(j={data:i||[],event_data:n,fn_index:$,trigger_id:r},function(t,e){var n,r;let i=null==(r=null==(n=null==e?void 0:e.dependencies)?void 0:n.find((e=>e.id==t)))?void 0:r.queue;return null!=i?!i:!e.enable_queue}($,g))s({type:"status",endpoint:N,stage:"pending",queue:!1,fn_index:$,time:new Date}),A(`${g.root}${P}/run${N.startsWith("/")?N:`/${N}`}${Y?"?"+Y:""}`,{...j,session_hash:w}).then((([t,e])=>{const i=t.data;200==e?(s({type:"data",endpoint:N,fn_index:$,data:ae(i,O,g.components,"output",T.with_null_state),time:new Date,event_data:n,trigger_id:r}),t.render_config&&J(t.render_config),s({type:"status",endpoint:N,fn_index:$,stage:"complete",eta:t.average_duration,queue:!1,time:new Date})):s({type:"status",stage:"error",endpoint:N,fn_index:$,message:t.error,queue:!1,time:new Date})})).catch((t=>{s({type:"status",stage:"error",message:t.message,endpoint:N,fn_index:$,queue:!1,time:new Date})}));else if("ws"==C){const{ws_protocol:t,host:e}=await Jt(d,f);s({type:"status",stage:"pending",queue:!0,endpoint:N,fn_index:$,time:new Date});let i=new URL(`${t}://${Nt(e,g.path,!0)}/queue/join${Y?"?"+Y:""}`);this.jwt&&i.searchParams.set("__sign",this.jwt),x=new WebSocket(i),x.onclose=t=>{t.wasClean||s({type:"status",stage:"error",broken:!0,message:Ot,queue:!0,endpoint:N,fn_index:$,time:new Date})},x.onmessage=function(t){const e=JSON.parse(t.data),{type:i,status:o,data:a}=Zt(e,q[$]);if("update"===i&&o&&!z)s({type:"status",endpoint:N,fn_index:$,time:new Date,...o}),"error"===o.stage&&x.close();else{if("hash"===i)return void x.send(JSON.stringify({fn_index:$,session_hash:w}));"data"===i?x.send(JSON.stringify({...j,session_hash:w})):"complete"===i?z=o:"log"===i?s({type:"log",title:a.title,log:a.log,level:a.level,endpoint:N,duration:a.duration,visible:a.visible,fn_index:$}):"generating"===i&&s({type:"status",time:new Date,...o,stage:null==o?void 0:o.stage,queue:!0,endpoint:N,fn_index:$})}a&&(s({type:"data",time:new Date,data:ae(a.data,O,g.components,"output",T.with_null_state),endpoint:N,fn_index:$,event_data:n,trigger_id:r}),z&&(s({type:"status",time:new Date,...z,stage:null==o?void 0:o.stage,queue:!0,endpoint:N,fn_index:$}),x.close()))},xt(g.version||"2.0.0","3.6")<0&&addEventListener("open",(()=>x.send(JSON.stringify({hash:w}))))}else if("sse"==C){s({type:"status",stage:"pending",queue:!0,endpoint:N,fn_index:$,time:new Date});var o=new URLSearchParams({fn_index:$.toString(),session_hash:w}).toString();let t=new URL(`${g.root}${P}/${kt}?${Y?Y+"&":""}${o}`);if(this.jwt&&t.searchParams.set("__sign",this.jwt),k=this.stream(t),!k)return Promise.reject(new Error("Cannot connect to SSE endpoint: "+t.toString()));k.onmessage=async function(t){const e=JSON.parse(t.data),{type:i,status:o,data:u}=Zt(e,q[$]);if("update"===i&&o&&!z)s({type:"status",endpoint:N,fn_index:$,time:new Date,...o}),"error"===o.stage&&(null==k||k.close(),a());else if("data"===i){let[t,e]=await A(`${g.root}${P}/queue/data`,{...j,session_hash:w,event_id:L});200!==e&&(s({type:"status",stage:"error",message:Ot,queue:!0,endpoint:N,fn_index:$,time:new Date}),null==k||k.close(),a())}else"complete"===i?z=o:"log"===i?s({type:"log",title:u.title,log:u.log,level:u.level,endpoint:N,duration:u.duration,visible:u.visible,fn_index:$}):"generating"!==i&&"streaming"!==i||s({type:"status",time:new Date,...o,stage:null==o?void 0:o.stage,queue:!0,endpoint:N,fn_index:$});u&&(s({type:"data",time:new Date,data:ae(u.data,O,g.components,"output",T.with_null_state),endpoint:N,fn_index:$,event_data:n,trigger_id:r}),z&&(s({type:"status",time:new Date,...z,stage:null==o?void 0:o.stage,queue:!0,endpoint:N,fn_index:$}),null==k||k.close(),a()))}}else if("sse_v1"==C||"sse_v2"==C||"sse_v2.1"==C||"sse_v3"==C){s({type:"status",stage:"pending",queue:!0,endpoint:N,fn_index:$,time:new Date});let t="";"undefined"!=typeof window&&"undefined"!=typeof document&&(t=null==(e=null==window?void 0:window.location)?void 0:e.hostname);let n="dev.spaces.huggingface.tech";const r=t.includes(".dev.")?`https://moon-${t.split(".")[1]}.${n}`:"https://huggingface.co",i="undefined"!=typeof window&&"undefined"!=typeof document&&window.parent!=window&&window.supports_zerogpu_headers?function(t,e){return new Promise(((n,r)=>{const i=new MessageChannel;i.port1.onmessage=({data:t})=>{i.port1.close(),n(t)},window.parent.postMessage(t,e,[i.port2])}))}("zerogpu-headers",r):Promise.resolve(null);i.then((t=>A(`${g.root}${P}/queue/join?${Y}`,{...j,session_hash:w},t))).then((async([t,e])=>{if(503===e)s({type:"status",stage:"error",message:Ut,queue:!0,endpoint:N,fn_index:$,time:new Date});else if(200!==e)s({type:"status",stage:"error",message:Ot,queue:!0,endpoint:N,fn_index:$,time:new Date});else{L=t.event_id,D=L;let e=async function(t){try{const{type:e,status:n,data:r,original_msg:i}=Zt(t,q[$]);if("heartbeat"==e)return;if("update"===e&&n&&!z)s({type:"status",endpoint:N,fn_index:$,time:new Date,original_msg:i,...n});else if("complete"===e)z=n;else if("unexpected_error"==e)console.error("Unexpected error",null==n?void 0:n.message),s({type:"status",stage:"error",message:(null==n?void 0:n.message)||"An Unexpected Error Occurred!",queue:!0,endpoint:N,fn_index:$,time:new Date});else{if("log"===e)return void s({type:"log",title:r.title,log:r.log,level:r.level,endpoint:N,duration:r.duration,visible:r.visible,fn_index:$});"generating"!==e&&"streaming"!==e||(s({type:"status",time:new Date,...n,stage:null==n?void 0:n.stage,queue:!0,endpoint:N,fn_index:$}),r&&"stream"!==O.connection&&["sse_v2","sse_v2.1","sse_v3"].includes(C)&&Te(b,L,r))}r&&(s({type:"data",time:new Date,data:ae(r.data,O,g.components,"output",T.with_null_state),endpoint:N,fn_index:$}),r.render_config&&await J(r.render_config),z&&(s({type:"status",time:new Date,...z,stage:null==n?void 0:n.stage,queue:!0,endpoint:N,fn_index:$}),a())),"complete"!==(null==n?void 0:n.stage)&&"error"!==(null==n?void 0:n.stage)||(E[L]&&delete E[L],L in b&&delete b[L])}catch(t){console.error("Unexpected client exception",t),s({type:"status",stage:"error",message:"An Unexpected Error Occurred!",queue:!0,endpoint:N,fn_index:$,time:new Date}),["sse_v2","sse_v2.1","sse_v3"].includes(C)&&(Ae(y,R.abort_controller),y.open=!1,a())}};L in v&&(v[L].forEach((t=>e(t))),delete v[L]),E[L]=e,S.add(L),y.open||await this.open_stream()}}))}}));let G=!1;const H=[],V=[],Z={[Symbol.asyncIterator]:()=>Z,next:l,throw:async t=>(c(t),l()),return:async()=>(a(),l()),cancel:F,event_id:I};return Z}catch(K){throw console.error("Submit function encountered an error:",K),K}}Et=new WeakMap;class xe{constructor(t,e={events:["data"]}){At(this,"app_reference"),At(this,"options"),At(this,"config"),At(this,"api_prefix",""),At(this,"api_info"),At(this,"api_map",{}),At(this,"session_hash",Math.random().toString(36).substring(2)),At(this,"jwt",!1),At(this,"last_status",{}),At(this,"cookies",null),At(this,"stream_status",{open:!1}),At(this,"pending_stream_messages",{}),At(this,"pending_diff_streams",{}),At(this,"event_callbacks",{}),At(this,"unclosed_events",new Set),At(this,"heartbeat_event",null),At(this,"abort_controller",null),At(this,"stream_instance",null),At(this,"current_payload"),At(this,"ws_map",{}),At(this,"view_api"),At(this,"upload_files"),At(this,"upload"),At(this,"handle_blob"),At(this,"post_data"),At(this,"submit"),At(this,"predict"),At(this,"open_stream"),At(this,"resolve_config"),At(this,"resolve_cookies"),this.app_reference=t,e.events||(e.events=["data"]),this.options=e,this.current_payload={},this.view_api=Kt.bind(this),this.upload_files=Qt.bind(this),this.handle_blob=ue.bind(this),this.post_data=le.bind(this),this.submit=Re.bind(this),this.predict=fe.bind(this),this.open_stream=Se.bind(this),this.resolve_config=Lt.bind(this),this.resolve_cookies=zt.bind(this),this.upload=Xt.bind(this),this.fetch=this.fetch.bind(this),this.handle_space_success=this.handle_space_success.bind(this),this.stream=this.stream.bind(this)}fetch(t,e){const n=new Headers((null==e?void 0:e.headers)||{});if(this&&this.cookies&&n.append("Cookie",this.cookies),this&&this.options.headers)for(const t in this.options.headers)n.append(t,this.options.headers[t]);return fetch(t,{...e,headers:n})}stream(t){const e=new Headers;if(this&&this.cookies&&e.append("Cookie",this.cookies),this&&this.options.headers)for(const t in this.options.headers)e.append(t,this.options.headers[t]);return this.abort_controller=new AbortController,this.stream_instance=Pe(t.toString(),{credentials:"include",headers:e,signal:this.abort_controller.signal}),this.stream_instance}async init(){var e;if(!("undefined"!=typeof window&&"WebSocket"in window||t.WebSocket)){const e=await import("/npm/@gradio/[email protected]/dist/wrapper-CviSselG.js/+esm");t.WebSocket=e.WebSocket}this.options.auth&&await this.resolve_cookies(),await this._resolve_config().then((({config:t})=>this._resolve_hearbeat(t))),this.api_info=await this.view_api(),this.api_map=function(t){let e={};return t.forEach((({api_name:t,id:n})=>{t&&(e[t]=n)})),e}((null==(e=this.config)?void 0:e.dependencies)||[])}async _resolve_hearbeat(t){if(t&&(this.config=t,this.api_prefix=t.api_prefix||"",this.config&&this.config.connect_heartbeat&&this.config.space_id&&this.options.hf_token&&(this.jwt=await jt(this.config.space_id,this.options.hf_token,this.cookies))),t.space_id&&this.options.hf_token&&(this.jwt=await jt(t.space_id,this.options.hf_token)),this.config&&this.config.connect_heartbeat){const t=new URL(`${this.config.root}${this.api_prefix}/heartbeat/${this.session_hash}`);this.jwt&&t.searchParams.set("__sign",this.jwt),this.heartbeat_event||(this.heartbeat_event=this.stream(t))}}static async connect(t,e={events:["data"]}){const n=new this(t,e);return await n.init(),n}close(){Ae(this.stream_status,this.abort_controller)}set_current_payload(t){this.current_payload=t}static async duplicate(t,e={events:["data"]}){return _e(t,e)}async _resolve_config(){const{http_protocol:t,host:e,space_id:n}=await Jt(this.app_reference,this.options.hf_token),{status_callback:r}=this.options;let i;n&&r&&await de(n,r);try{if(i=await this.resolve_config(`${t}//${e}`),!i)throw new Error(Bt);return this.config_success(i)}catch(t){if(!n||!r)throw r&&r({status:"error",message:"Could not load this space.",load_status:"error",detail:"NOT_FOUND"}),Error(t);pe(n,Ft.test(n)?"space_name":"subdomain",this.handle_space_success)}}async config_success(t){if(this.config=t,this.api_prefix=t.api_prefix||"","undefined"!=typeof window&&"undefined"!=typeof document&&"https:"===window.location.protocol&&(this.config.root=this.config.root.replace("http://","https://")),this.config.auth_required)return this.prepare_return_obj();try{this.api_info=await this.view_api()}catch(t){console.error("Could not get API info. "+t.message)}return this.prepare_return_obj()}async handle_space_success(t){var e;if(!this)throw new Error(Bt);const{status_callback:n}=this.options;if(n&&n(t),"running"===t.status)try{if(this.config=await this._resolve_config(),this.api_prefix=(null==(e=null==this?void 0:this.config)?void 0:e.api_prefix)||"",!this.config)throw new Error(Bt);return await this.config_success(this.config)}catch(t){throw n&&n({status:"error",message:"Could not load this space.",load_status:"error",detail:"NOT_FOUND"}),t}}async component_server(t,e,n){var r;if(!this.config)throw new Error(Bt);const i={},{hf_token:o}=this.options,{session_hash:s}=this;let a;o&&(i.Authorization=`Bearer ${this.options.hf_token}`);let u,c=this.config.components.find((e=>e.id===t));if(a=(null==(r=null==c?void 0:c.props)?void 0:r.root_url)?c.props.root_url:this.config.root,"binary"in n){u=new FormData;for(const t in n.data)"binary"!==t&&u.append(t,n.data[t]);u.set("component_id",t.toString()),u.set("fn_name",e),u.set("session_hash",s)}else u=JSON.stringify({data:n,component_id:t,fn_name:e,session_hash:s}),i["Content-Type"]="application/json";o&&(i.Authorization=`Bearer ${o}`);try{const t=await this.fetch(`${a}${this.api_prefix}/component_server/`,{method:"POST",body:u,headers:i,credentials:"include"});if(!t.ok)throw new Error("Could not connect to component server: "+t.statusText);return await t.json()}catch(t){console.warn(t)}}set_cookies(t){this.cookies=Mt(t).join("; ")}prepare_return_obj(){return{config:this.config,predict:this.predict,submit:this.submit,view_api:this.view_api,component_server:this.component_server}}async connect_ws(t){return new Promise(((e,n)=>{let r;try{r=new WebSocket(t)}catch(e){return void(this.ws_map[t]="failed")}r.onopen=()=>{e()},r.onerror=n=>{console.error("WebSocket error:",n),this.close_ws(t),this.ws_map[t]="failed",e()},r.onclose=()=>{delete this.ws_map[t],this.ws_map[t]="failed"},r.onmessage=t=>{},this.ws_map[t]=r}))}async send_ws_message(t,e){t in this.ws_map||await this.connect_ws(t);const n=this.ws_map[t];n instanceof WebSocket?n.send(JSON.stringify(e)):this.post_data(t,e)}async close_ws(t){if(t in this.ws_map){const e=this.ws_map[t];e instanceof WebSocket&&(e.close(),delete this.ws_map[t])}}}async function ke(t,e={events:["data"]}){return await xe.connect(t,e)}async function $e(t,e){return await xe.duplicate(t,e)}export{xe as Client,ee as FileData,ke as client,$e as duplicate,se as handle_file,fe as predict,te as prepare_files,Re as submit,Xt as upload,Qt as upload_files};export default null;
|
8 |
+
//# sourceMappingURL=/sm/4d92fd0f682c91bdc5f31b37e659b6bd0cf5b64ff6c4edc2d0c0cb3f4bfa1850.map
|
libs/three.js/0.172.0/jsm/Addons.js
ADDED
@@ -0,0 +1,287 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
export * from './animation/AnimationClipCreator.js';
|
2 |
+
export * from './animation/CCDIKSolver.js';
|
3 |
+
|
4 |
+
export { default as WebGL } from './capabilities/WebGL.js';
|
5 |
+
|
6 |
+
export * from './controls/ArcballControls.js';
|
7 |
+
export * from './controls/DragControls.js';
|
8 |
+
export * from './controls/FirstPersonControls.js';
|
9 |
+
export * from './controls/FlyControls.js';
|
10 |
+
export * from './controls/MapControls.js';
|
11 |
+
export * from './controls/OrbitControls.js';
|
12 |
+
export * from './controls/PointerLockControls.js';
|
13 |
+
export * from './controls/TrackballControls.js';
|
14 |
+
export * from './controls/TransformControls.js';
|
15 |
+
|
16 |
+
export * from './csm/CSM.js';
|
17 |
+
export * from './csm/CSMFrustum.js';
|
18 |
+
export * from './csm/CSMHelper.js';
|
19 |
+
export * from './csm/CSMShader.js';
|
20 |
+
|
21 |
+
export * as Curves from './curves/CurveExtras.js';
|
22 |
+
export * from './curves/NURBSCurve.js';
|
23 |
+
export * from './curves/NURBSSurface.js';
|
24 |
+
export * from './curves/NURBSVolume.js';
|
25 |
+
export * as NURBSUtils from './curves/NURBSUtils.js';
|
26 |
+
|
27 |
+
export * from './effects/AnaglyphEffect.js';
|
28 |
+
export * from './effects/AsciiEffect.js';
|
29 |
+
export * from './effects/OutlineEffect.js';
|
30 |
+
export * from './effects/ParallaxBarrierEffect.js';
|
31 |
+
export * from './effects/PeppersGhostEffect.js';
|
32 |
+
export * from './effects/StereoEffect.js';
|
33 |
+
|
34 |
+
export * from './environments/DebugEnvironment.js';
|
35 |
+
export * from './environments/RoomEnvironment.js';
|
36 |
+
|
37 |
+
export * from './exporters/DRACOExporter.js';
|
38 |
+
export * from './exporters/EXRExporter.js';
|
39 |
+
export * from './exporters/GLTFExporter.js';
|
40 |
+
export * from './exporters/KTX2Exporter.js';
|
41 |
+
export * from './exporters/OBJExporter.js';
|
42 |
+
export * from './exporters/PLYExporter.js';
|
43 |
+
export * from './exporters/STLExporter.js';
|
44 |
+
export * from './exporters/USDZExporter.js';
|
45 |
+
|
46 |
+
export * from './geometries/BoxLineGeometry.js';
|
47 |
+
export * from './geometries/ConvexGeometry.js';
|
48 |
+
export * from './geometries/DecalGeometry.js';
|
49 |
+
export * from './geometries/ParametricGeometries.js';
|
50 |
+
export * from './geometries/ParametricGeometry.js';
|
51 |
+
export * from './geometries/RoundedBoxGeometry.js';
|
52 |
+
export * from './geometries/TeapotGeometry.js';
|
53 |
+
export * from './geometries/TextGeometry.js';
|
54 |
+
|
55 |
+
export * from './helpers/LightProbeHelper.js';
|
56 |
+
export * from './helpers/OctreeHelper.js';
|
57 |
+
export * from './helpers/PositionalAudioHelper.js';
|
58 |
+
export * from './helpers/RectAreaLightHelper.js';
|
59 |
+
export * from './helpers/TextureHelper.js';
|
60 |
+
export * from './helpers/VertexNormalsHelper.js';
|
61 |
+
export * from './helpers/VertexTangentsHelper.js';
|
62 |
+
export * from './helpers/ViewHelper.js';
|
63 |
+
|
64 |
+
export * from './interactive/HTMLMesh.js';
|
65 |
+
export * from './interactive/InteractiveGroup.js';
|
66 |
+
export * from './interactive/SelectionBox.js';
|
67 |
+
export * from './interactive/SelectionHelper.js';
|
68 |
+
|
69 |
+
export * from './lights/LightProbeGenerator.js';
|
70 |
+
export * from './lights/RectAreaLightTexturesLib.js';
|
71 |
+
export * from './lights/RectAreaLightUniformsLib.js';
|
72 |
+
|
73 |
+
export * from './lines/Line2.js';
|
74 |
+
export * from './lines/LineGeometry.js';
|
75 |
+
export * from './lines/LineMaterial.js';
|
76 |
+
export * from './lines/LineSegments2.js';
|
77 |
+
export * from './lines/LineSegmentsGeometry.js';
|
78 |
+
export * from './lines/Wireframe.js';
|
79 |
+
export * from './lines/WireframeGeometry2.js';
|
80 |
+
|
81 |
+
export * from './loaders/3DMLoader.js';
|
82 |
+
export * from './loaders/3MFLoader.js';
|
83 |
+
export * from './loaders/AMFLoader.js';
|
84 |
+
export * from './loaders/BVHLoader.js';
|
85 |
+
export * from './loaders/ColladaLoader.js';
|
86 |
+
export * from './loaders/DDSLoader.js';
|
87 |
+
export * from './loaders/DRACOLoader.js';
|
88 |
+
export * from './loaders/EXRLoader.js';
|
89 |
+
export * from './loaders/FBXLoader.js';
|
90 |
+
export * from './loaders/FontLoader.js';
|
91 |
+
export * from './loaders/GCodeLoader.js';
|
92 |
+
export * from './loaders/GLTFLoader.js';
|
93 |
+
export * from './loaders/HDRCubeTextureLoader.js';
|
94 |
+
export * from './loaders/IESLoader.js';
|
95 |
+
export * from './loaders/KMZLoader.js';
|
96 |
+
export * from './loaders/KTX2Loader.js';
|
97 |
+
export * from './loaders/KTXLoader.js';
|
98 |
+
export * from './loaders/LDrawLoader.js';
|
99 |
+
export * from './loaders/LUT3dlLoader.js';
|
100 |
+
export * from './loaders/LUTCubeLoader.js';
|
101 |
+
export * from './loaders/LWOLoader.js';
|
102 |
+
export * from './loaders/LottieLoader.js';
|
103 |
+
export * from './loaders/MD2Loader.js';
|
104 |
+
export * from './loaders/MDDLoader.js';
|
105 |
+
export * from './loaders/MTLLoader.js';
|
106 |
+
export * from './loaders/NRRDLoader.js';
|
107 |
+
export * from './loaders/OBJLoader.js';
|
108 |
+
export * from './loaders/PCDLoader.js';
|
109 |
+
export * from './loaders/PDBLoader.js';
|
110 |
+
export * from './loaders/PLYLoader.js';
|
111 |
+
export * from './loaders/PVRLoader.js';
|
112 |
+
export * from './loaders/RGBELoader.js';
|
113 |
+
export * from './loaders/UltraHDRLoader.js';
|
114 |
+
export * from './loaders/RGBMLoader.js';
|
115 |
+
export * from './loaders/STLLoader.js';
|
116 |
+
export * from './loaders/SVGLoader.js';
|
117 |
+
export * from './loaders/TDSLoader.js';
|
118 |
+
export * from './loaders/TGALoader.js';
|
119 |
+
export * from './loaders/TIFFLoader.js';
|
120 |
+
export * from './loaders/TTFLoader.js';
|
121 |
+
export * from './loaders/USDZLoader.js';
|
122 |
+
export * from './loaders/VOXLoader.js';
|
123 |
+
export * from './loaders/VRMLLoader.js';
|
124 |
+
export * from './loaders/VTKLoader.js';
|
125 |
+
export * from './loaders/XYZLoader.js';
|
126 |
+
|
127 |
+
export * from './materials/MeshGouraudMaterial.js';
|
128 |
+
export * from './materials/LDrawConditionalLineMaterial.js';
|
129 |
+
export * from './materials/MeshPostProcessingMaterial.js';
|
130 |
+
|
131 |
+
export * from './math/Capsule.js';
|
132 |
+
export * from './math/ColorConverter.js';
|
133 |
+
export * from './math/ConvexHull.js';
|
134 |
+
export * from './math/ImprovedNoise.js';
|
135 |
+
export * from './math/Lut.js';
|
136 |
+
export * from './math/MeshSurfaceSampler.js';
|
137 |
+
export * from './math/OBB.js';
|
138 |
+
export * from './math/Octree.js';
|
139 |
+
export * from './math/SimplexNoise.js';
|
140 |
+
|
141 |
+
export * from './misc/ConvexObjectBreaker.js';
|
142 |
+
export * from './misc/GPUComputationRenderer.js';
|
143 |
+
export * from './misc/Gyroscope.js';
|
144 |
+
export * from './misc/MD2Character.js';
|
145 |
+
export * from './misc/MD2CharacterComplex.js';
|
146 |
+
export * from './misc/MorphAnimMesh.js';
|
147 |
+
export * from './misc/MorphBlendMesh.js';
|
148 |
+
export * from './misc/ProgressiveLightMap.js';
|
149 |
+
export * from './misc/RollerCoaster.js';
|
150 |
+
export * from './misc/Timer.js';
|
151 |
+
export * from './misc/TubePainter.js';
|
152 |
+
export * from './misc/Volume.js';
|
153 |
+
export * from './misc/VolumeSlice.js';
|
154 |
+
|
155 |
+
export * from './modifiers/CurveModifier.js';
|
156 |
+
export * from './modifiers/EdgeSplitModifier.js';
|
157 |
+
export * from './modifiers/SimplifyModifier.js';
|
158 |
+
export * from './modifiers/TessellateModifier.js';
|
159 |
+
|
160 |
+
export * from './objects/GroundedSkybox.js';
|
161 |
+
export * from './objects/Lensflare.js';
|
162 |
+
export * from './objects/MarchingCubes.js';
|
163 |
+
export * from './objects/Reflector.js';
|
164 |
+
export * from './objects/ReflectorForSSRPass.js';
|
165 |
+
export * from './objects/Refractor.js';
|
166 |
+
export * from './objects/ShadowMesh.js';
|
167 |
+
export * from './objects/Sky.js';
|
168 |
+
export * from './objects/Water.js';
|
169 |
+
export { Water as Water2 } from './objects/Water2.js';
|
170 |
+
|
171 |
+
export * from './physics/AmmoPhysics.js';
|
172 |
+
export * from './physics/RapierPhysics.js';
|
173 |
+
|
174 |
+
export * from './postprocessing/AfterimagePass.js';
|
175 |
+
export * from './postprocessing/BloomPass.js';
|
176 |
+
export * from './postprocessing/BokehPass.js';
|
177 |
+
export * from './postprocessing/ClearPass.js';
|
178 |
+
export * from './postprocessing/CubeTexturePass.js';
|
179 |
+
export * from './postprocessing/DotScreenPass.js';
|
180 |
+
export * from './postprocessing/EffectComposer.js';
|
181 |
+
export * from './postprocessing/FilmPass.js';
|
182 |
+
export * from './postprocessing/GlitchPass.js';
|
183 |
+
export * from './postprocessing/GTAOPass.js';
|
184 |
+
export * from './postprocessing/HalftonePass.js';
|
185 |
+
export * from './postprocessing/LUTPass.js';
|
186 |
+
export * from './postprocessing/MaskPass.js';
|
187 |
+
export * from './postprocessing/OutlinePass.js';
|
188 |
+
export * from './postprocessing/OutputPass.js';
|
189 |
+
export * from './postprocessing/Pass.js';
|
190 |
+
export * from './postprocessing/RenderPass.js';
|
191 |
+
export * from './postprocessing/RenderPixelatedPass.js';
|
192 |
+
export * from './postprocessing/SAOPass.js';
|
193 |
+
export * from './postprocessing/SMAAPass.js';
|
194 |
+
export * from './postprocessing/SSAARenderPass.js';
|
195 |
+
export * from './postprocessing/SSAOPass.js';
|
196 |
+
export * from './postprocessing/SSRPass.js';
|
197 |
+
export * from './postprocessing/SavePass.js';
|
198 |
+
export * from './postprocessing/ShaderPass.js';
|
199 |
+
export * from './postprocessing/TAARenderPass.js';
|
200 |
+
export * from './postprocessing/TexturePass.js';
|
201 |
+
export * from './postprocessing/UnrealBloomPass.js';
|
202 |
+
|
203 |
+
export * from './renderers/CSS2DRenderer.js';
|
204 |
+
export * from './renderers/CSS3DRenderer.js';
|
205 |
+
export * from './renderers/Projector.js';
|
206 |
+
export * from './renderers/SVGRenderer.js';
|
207 |
+
|
208 |
+
export * from './shaders/ACESFilmicToneMappingShader.js';
|
209 |
+
export * from './shaders/AfterimageShader.js';
|
210 |
+
export * from './shaders/BasicShader.js';
|
211 |
+
export * from './shaders/BleachBypassShader.js';
|
212 |
+
export * from './shaders/BlendShader.js';
|
213 |
+
export * from './shaders/BokehShader.js';
|
214 |
+
export { BokehShader as BokehShader2 } from './shaders/BokehShader2.js';
|
215 |
+
export * from './shaders/BrightnessContrastShader.js';
|
216 |
+
export * from './shaders/ColorCorrectionShader.js';
|
217 |
+
export * from './shaders/ColorifyShader.js';
|
218 |
+
export * from './shaders/ConvolutionShader.js';
|
219 |
+
export * from './shaders/CopyShader.js';
|
220 |
+
export * from './shaders/DOFMipMapShader.js';
|
221 |
+
export * from './shaders/DepthLimitedBlurShader.js';
|
222 |
+
export * from './shaders/DigitalGlitch.js';
|
223 |
+
export * from './shaders/DotScreenShader.js';
|
224 |
+
export * from './shaders/ExposureShader.js';
|
225 |
+
export * from './shaders/FXAAShader.js';
|
226 |
+
export * from './shaders/FilmShader.js';
|
227 |
+
export * from './shaders/FocusShader.js';
|
228 |
+
export * from './shaders/FreiChenShader.js';
|
229 |
+
export * from './shaders/GammaCorrectionShader.js';
|
230 |
+
export * from './shaders/GodRaysShader.js';
|
231 |
+
export * from './shaders/GTAOShader.js';
|
232 |
+
export * from './shaders/HalftoneShader.js';
|
233 |
+
export * from './shaders/HorizontalBlurShader.js';
|
234 |
+
export * from './shaders/HorizontalTiltShiftShader.js';
|
235 |
+
export * from './shaders/HueSaturationShader.js';
|
236 |
+
export * from './shaders/KaleidoShader.js';
|
237 |
+
export * from './shaders/LuminosityHighPassShader.js';
|
238 |
+
export * from './shaders/LuminosityShader.js';
|
239 |
+
export * from './shaders/MirrorShader.js';
|
240 |
+
export * from './shaders/NormalMapShader.js';
|
241 |
+
export * from './shaders/OutputShader.js';
|
242 |
+
export * from './shaders/RGBShiftShader.js';
|
243 |
+
export * from './shaders/SAOShader.js';
|
244 |
+
export * from './shaders/SMAAShader.js';
|
245 |
+
export * from './shaders/SSAOShader.js';
|
246 |
+
export * from './shaders/SSRShader.js';
|
247 |
+
export * from './shaders/SepiaShader.js';
|
248 |
+
export * from './shaders/SobelOperatorShader.js';
|
249 |
+
export * from './shaders/SubsurfaceScatteringShader.js';
|
250 |
+
export * from './shaders/TechnicolorShader.js';
|
251 |
+
export * from './shaders/ToonShader.js';
|
252 |
+
export * from './shaders/TriangleBlurShader.js';
|
253 |
+
export * from './shaders/UnpackDepthRGBAShader.js';
|
254 |
+
export * from './shaders/VelocityShader.js';
|
255 |
+
export * from './shaders/VerticalBlurShader.js';
|
256 |
+
export * from './shaders/VerticalTiltShiftShader.js';
|
257 |
+
export * from './shaders/VignetteShader.js';
|
258 |
+
export * from './shaders/VolumeShader.js';
|
259 |
+
export * from './shaders/WaterRefractionShader.js';
|
260 |
+
|
261 |
+
export * from './textures/FlakesTexture.js';
|
262 |
+
|
263 |
+
export * as BufferGeometryUtils from './utils/BufferGeometryUtils.js';
|
264 |
+
export * as CameraUtils from './utils/CameraUtils.js';
|
265 |
+
export * as GeometryCompressionUtils from './utils/GeometryCompressionUtils.js';
|
266 |
+
export * as GeometryUtils from './utils/GeometryUtils.js';
|
267 |
+
export * from './utils/LDrawUtils.js';
|
268 |
+
export * as SceneUtils from './utils/SceneUtils.js';
|
269 |
+
export * from './utils/ShadowMapViewer.js';
|
270 |
+
export * as SkeletonUtils from './utils/SkeletonUtils.js';
|
271 |
+
export * as SortUtils from './utils/SortUtils.js';
|
272 |
+
export * from './utils/WebGLTextureUtils.js';
|
273 |
+
export * from './utils/UVsDebug.js';
|
274 |
+
export * from './utils/WorkerPool.js';
|
275 |
+
|
276 |
+
export * from './webxr/ARButton.js';
|
277 |
+
export * from './webxr/OculusHandModel.js';
|
278 |
+
export * from './webxr/OculusHandPointerModel.js';
|
279 |
+
export * from './webxr/Text2D.js';
|
280 |
+
export * from './webxr/VRButton.js';
|
281 |
+
export * from './webxr/XRButton.js';
|
282 |
+
export * from './webxr/XRControllerModelFactory.js';
|
283 |
+
export * from './webxr/XREstimatedLight.js';
|
284 |
+
export * from './webxr/XRHandMeshModel.js';
|
285 |
+
export * from './webxr/XRHandModelFactory.js';
|
286 |
+
export * from './webxr/XRHandPrimitiveModel.js';
|
287 |
+
export * from './webxr/XRPlanes.js';
|
libs/three.js/0.172.0/jsm/animation/AnimationClipCreator.js
ADDED
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
AnimationClip,
|
3 |
+
BooleanKeyframeTrack,
|
4 |
+
ColorKeyframeTrack,
|
5 |
+
NumberKeyframeTrack,
|
6 |
+
Vector3,
|
7 |
+
VectorKeyframeTrack
|
8 |
+
} from 'three';
|
9 |
+
|
10 |
+
class AnimationClipCreator {
|
11 |
+
|
12 |
+
static CreateRotationAnimation( period, axis = 'x' ) {
|
13 |
+
|
14 |
+
const times = [ 0, period ], values = [ 0, 360 ];
|
15 |
+
|
16 |
+
const trackName = '.rotation[' + axis + ']';
|
17 |
+
|
18 |
+
const track = new NumberKeyframeTrack( trackName, times, values );
|
19 |
+
|
20 |
+
return new AnimationClip( null, period, [ track ] );
|
21 |
+
|
22 |
+
}
|
23 |
+
|
24 |
+
static CreateScaleAxisAnimation( period, axis = 'x' ) {
|
25 |
+
|
26 |
+
const times = [ 0, period ], values = [ 0, 1 ];
|
27 |
+
|
28 |
+
const trackName = '.scale[' + axis + ']';
|
29 |
+
|
30 |
+
const track = new NumberKeyframeTrack( trackName, times, values );
|
31 |
+
|
32 |
+
return new AnimationClip( null, period, [ track ] );
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
static CreateShakeAnimation( duration, shakeScale ) {
|
37 |
+
|
38 |
+
const times = [], values = [], tmp = new Vector3();
|
39 |
+
|
40 |
+
for ( let i = 0; i < duration * 10; i ++ ) {
|
41 |
+
|
42 |
+
times.push( i / 10 );
|
43 |
+
|
44 |
+
tmp.set( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).
|
45 |
+
multiply( shakeScale ).
|
46 |
+
toArray( values, values.length );
|
47 |
+
|
48 |
+
}
|
49 |
+
|
50 |
+
const trackName = '.position';
|
51 |
+
|
52 |
+
const track = new VectorKeyframeTrack( trackName, times, values );
|
53 |
+
|
54 |
+
return new AnimationClip( null, duration, [ track ] );
|
55 |
+
|
56 |
+
}
|
57 |
+
|
58 |
+
static CreatePulsationAnimation( duration, pulseScale ) {
|
59 |
+
|
60 |
+
const times = [], values = [], tmp = new Vector3();
|
61 |
+
|
62 |
+
for ( let i = 0; i < duration * 10; i ++ ) {
|
63 |
+
|
64 |
+
times.push( i / 10 );
|
65 |
+
|
66 |
+
const scaleFactor = Math.random() * pulseScale;
|
67 |
+
tmp.set( scaleFactor, scaleFactor, scaleFactor ).
|
68 |
+
toArray( values, values.length );
|
69 |
+
|
70 |
+
}
|
71 |
+
|
72 |
+
const trackName = '.scale';
|
73 |
+
|
74 |
+
const track = new VectorKeyframeTrack( trackName, times, values );
|
75 |
+
|
76 |
+
return new AnimationClip( null, duration, [ track ] );
|
77 |
+
|
78 |
+
}
|
79 |
+
|
80 |
+
static CreateVisibilityAnimation( duration ) {
|
81 |
+
|
82 |
+
const times = [ 0, duration / 2, duration ], values = [ true, false, true ];
|
83 |
+
|
84 |
+
const trackName = '.visible';
|
85 |
+
|
86 |
+
const track = new BooleanKeyframeTrack( trackName, times, values );
|
87 |
+
|
88 |
+
return new AnimationClip( null, duration, [ track ] );
|
89 |
+
|
90 |
+
}
|
91 |
+
|
92 |
+
static CreateMaterialColorAnimation( duration, colors ) {
|
93 |
+
|
94 |
+
const times = [], values = [],
|
95 |
+
timeStep = ( colors.length > 1 ) ? duration / ( colors.length - 1 ) : 0;
|
96 |
+
|
97 |
+
for ( let i = 0; i < colors.length; i ++ ) {
|
98 |
+
|
99 |
+
times.push( i * timeStep );
|
100 |
+
|
101 |
+
const color = colors[ i ];
|
102 |
+
values.push( color.r, color.g, color.b );
|
103 |
+
|
104 |
+
}
|
105 |
+
|
106 |
+
const trackName = '.material.color';
|
107 |
+
|
108 |
+
const track = new ColorKeyframeTrack( trackName, times, values );
|
109 |
+
|
110 |
+
return new AnimationClip( null, duration, [ track ] );
|
111 |
+
|
112 |
+
}
|
113 |
+
|
114 |
+
}
|
115 |
+
|
116 |
+
export { AnimationClipCreator };
|
libs/three.js/0.172.0/jsm/animation/CCDIKSolver.js
ADDED
@@ -0,0 +1,485 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BufferAttribute,
|
3 |
+
BufferGeometry,
|
4 |
+
Color,
|
5 |
+
Line,
|
6 |
+
LineBasicMaterial,
|
7 |
+
Matrix4,
|
8 |
+
Mesh,
|
9 |
+
MeshBasicMaterial,
|
10 |
+
Object3D,
|
11 |
+
Quaternion,
|
12 |
+
SphereGeometry,
|
13 |
+
Vector3
|
14 |
+
} from 'three';
|
15 |
+
|
16 |
+
const _q = new Quaternion();
|
17 |
+
const _targetPos = new Vector3();
|
18 |
+
const _targetVec = new Vector3();
|
19 |
+
const _effectorPos = new Vector3();
|
20 |
+
const _effectorVec = new Vector3();
|
21 |
+
const _linkPos = new Vector3();
|
22 |
+
const _invLinkQ = new Quaternion();
|
23 |
+
const _linkScale = new Vector3();
|
24 |
+
const _axis = new Vector3();
|
25 |
+
const _vector = new Vector3();
|
26 |
+
const _matrix = new Matrix4();
|
27 |
+
|
28 |
+
|
29 |
+
/**
|
30 |
+
* CCD Algorithm
|
31 |
+
* - https://web.archive.org/web/20221206080850/https://sites.google.com/site/auraliusproject/ccd-algorithm
|
32 |
+
*
|
33 |
+
* // ik parameter example
|
34 |
+
* //
|
35 |
+
* // target, effector, index in links are bone index in skeleton.bones.
|
36 |
+
* // the bones relation should be
|
37 |
+
* // <-- parent child -->
|
38 |
+
* // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector
|
39 |
+
* iks = [ {
|
40 |
+
* target: 1,
|
41 |
+
* effector: 2,
|
42 |
+
* links: [ { index: 5, limitation: new Vector3( 1, 0, 0 ) }, { index: 4, enabled: false }, { index : 3 } ],
|
43 |
+
* iteration: 10,
|
44 |
+
* minAngle: 0.0,
|
45 |
+
* maxAngle: 1.0,
|
46 |
+
* } ];
|
47 |
+
*/
|
48 |
+
|
49 |
+
class CCDIKSolver {
|
50 |
+
|
51 |
+
/**
|
52 |
+
* @param {THREE.SkinnedMesh} mesh
|
53 |
+
* @param {Array<Object>} iks
|
54 |
+
*/
|
55 |
+
constructor( mesh, iks = [] ) {
|
56 |
+
|
57 |
+
this.mesh = mesh;
|
58 |
+
this.iks = iks;
|
59 |
+
|
60 |
+
this._valid();
|
61 |
+
|
62 |
+
}
|
63 |
+
|
64 |
+
/**
|
65 |
+
* Update all IK bones.
|
66 |
+
*
|
67 |
+
* @return {CCDIKSolver}
|
68 |
+
*/
|
69 |
+
update() {
|
70 |
+
|
71 |
+
const iks = this.iks;
|
72 |
+
|
73 |
+
for ( let i = 0, il = iks.length; i < il; i ++ ) {
|
74 |
+
|
75 |
+
this.updateOne( iks[ i ] );
|
76 |
+
|
77 |
+
}
|
78 |
+
|
79 |
+
return this;
|
80 |
+
|
81 |
+
}
|
82 |
+
|
83 |
+
/**
|
84 |
+
* Update one IK bone
|
85 |
+
*
|
86 |
+
* @param {Object} ik parameter
|
87 |
+
* @return {CCDIKSolver}
|
88 |
+
*/
|
89 |
+
updateOne( ik ) {
|
90 |
+
|
91 |
+
const bones = this.mesh.skeleton.bones;
|
92 |
+
|
93 |
+
// for reference overhead reduction in loop
|
94 |
+
const math = Math;
|
95 |
+
|
96 |
+
const effector = bones[ ik.effector ];
|
97 |
+
const target = bones[ ik.target ];
|
98 |
+
|
99 |
+
// don't use getWorldPosition() here for the performance
|
100 |
+
// because it calls updateMatrixWorld( true ) inside.
|
101 |
+
_targetPos.setFromMatrixPosition( target.matrixWorld );
|
102 |
+
|
103 |
+
const links = ik.links;
|
104 |
+
const iteration = ik.iteration !== undefined ? ik.iteration : 1;
|
105 |
+
|
106 |
+
for ( let i = 0; i < iteration; i ++ ) {
|
107 |
+
|
108 |
+
let rotated = false;
|
109 |
+
|
110 |
+
for ( let j = 0, jl = links.length; j < jl; j ++ ) {
|
111 |
+
|
112 |
+
const link = bones[ links[ j ].index ];
|
113 |
+
|
114 |
+
// skip this link and following links
|
115 |
+
if ( links[ j ].enabled === false ) break;
|
116 |
+
|
117 |
+
const limitation = links[ j ].limitation;
|
118 |
+
const rotationMin = links[ j ].rotationMin;
|
119 |
+
const rotationMax = links[ j ].rotationMax;
|
120 |
+
|
121 |
+
// don't use getWorldPosition/Quaternion() here for the performance
|
122 |
+
// because they call updateMatrixWorld( true ) inside.
|
123 |
+
link.matrixWorld.decompose( _linkPos, _invLinkQ, _linkScale );
|
124 |
+
_invLinkQ.invert();
|
125 |
+
_effectorPos.setFromMatrixPosition( effector.matrixWorld );
|
126 |
+
|
127 |
+
// work in link world
|
128 |
+
_effectorVec.subVectors( _effectorPos, _linkPos );
|
129 |
+
_effectorVec.applyQuaternion( _invLinkQ );
|
130 |
+
_effectorVec.normalize();
|
131 |
+
|
132 |
+
_targetVec.subVectors( _targetPos, _linkPos );
|
133 |
+
_targetVec.applyQuaternion( _invLinkQ );
|
134 |
+
_targetVec.normalize();
|
135 |
+
|
136 |
+
let angle = _targetVec.dot( _effectorVec );
|
137 |
+
|
138 |
+
if ( angle > 1.0 ) {
|
139 |
+
|
140 |
+
angle = 1.0;
|
141 |
+
|
142 |
+
} else if ( angle < - 1.0 ) {
|
143 |
+
|
144 |
+
angle = - 1.0;
|
145 |
+
|
146 |
+
}
|
147 |
+
|
148 |
+
angle = math.acos( angle );
|
149 |
+
|
150 |
+
// skip if changing angle is too small to prevent vibration of bone
|
151 |
+
if ( angle < 1e-5 ) continue;
|
152 |
+
|
153 |
+
if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
|
154 |
+
|
155 |
+
angle = ik.minAngle;
|
156 |
+
|
157 |
+
}
|
158 |
+
|
159 |
+
if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
|
160 |
+
|
161 |
+
angle = ik.maxAngle;
|
162 |
+
|
163 |
+
}
|
164 |
+
|
165 |
+
_axis.crossVectors( _effectorVec, _targetVec );
|
166 |
+
_axis.normalize();
|
167 |
+
|
168 |
+
_q.setFromAxisAngle( _axis, angle );
|
169 |
+
link.quaternion.multiply( _q );
|
170 |
+
|
171 |
+
// TODO: re-consider the limitation specification
|
172 |
+
if ( limitation !== undefined ) {
|
173 |
+
|
174 |
+
let c = link.quaternion.w;
|
175 |
+
|
176 |
+
if ( c > 1.0 ) c = 1.0;
|
177 |
+
|
178 |
+
const c2 = math.sqrt( 1 - c * c );
|
179 |
+
link.quaternion.set( limitation.x * c2,
|
180 |
+
limitation.y * c2,
|
181 |
+
limitation.z * c2,
|
182 |
+
c );
|
183 |
+
|
184 |
+
}
|
185 |
+
|
186 |
+
if ( rotationMin !== undefined ) {
|
187 |
+
|
188 |
+
link.rotation.setFromVector3( _vector.setFromEuler( link.rotation ).max( rotationMin ) );
|
189 |
+
|
190 |
+
}
|
191 |
+
|
192 |
+
if ( rotationMax !== undefined ) {
|
193 |
+
|
194 |
+
link.rotation.setFromVector3( _vector.setFromEuler( link.rotation ).min( rotationMax ) );
|
195 |
+
|
196 |
+
}
|
197 |
+
|
198 |
+
link.updateMatrixWorld( true );
|
199 |
+
|
200 |
+
rotated = true;
|
201 |
+
|
202 |
+
}
|
203 |
+
|
204 |
+
if ( ! rotated ) break;
|
205 |
+
|
206 |
+
}
|
207 |
+
|
208 |
+
return this;
|
209 |
+
|
210 |
+
}
|
211 |
+
|
212 |
+
/**
|
213 |
+
* Creates Helper
|
214 |
+
*
|
215 |
+
* @param {number} sphereSize
|
216 |
+
* @return {CCDIKHelper}
|
217 |
+
*/
|
218 |
+
createHelper( sphereSize ) {
|
219 |
+
|
220 |
+
return new CCDIKHelper( this.mesh, this.iks, sphereSize );
|
221 |
+
|
222 |
+
}
|
223 |
+
|
224 |
+
// private methods
|
225 |
+
|
226 |
+
_valid() {
|
227 |
+
|
228 |
+
const iks = this.iks;
|
229 |
+
const bones = this.mesh.skeleton.bones;
|
230 |
+
|
231 |
+
for ( let i = 0, il = iks.length; i < il; i ++ ) {
|
232 |
+
|
233 |
+
const ik = iks[ i ];
|
234 |
+
const effector = bones[ ik.effector ];
|
235 |
+
const links = ik.links;
|
236 |
+
let link0, link1;
|
237 |
+
|
238 |
+
link0 = effector;
|
239 |
+
|
240 |
+
for ( let j = 0, jl = links.length; j < jl; j ++ ) {
|
241 |
+
|
242 |
+
link1 = bones[ links[ j ].index ];
|
243 |
+
|
244 |
+
if ( link0.parent !== link1 ) {
|
245 |
+
|
246 |
+
console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
|
247 |
+
|
248 |
+
}
|
249 |
+
|
250 |
+
link0 = link1;
|
251 |
+
|
252 |
+
}
|
253 |
+
|
254 |
+
}
|
255 |
+
|
256 |
+
}
|
257 |
+
|
258 |
+
}
|
259 |
+
|
260 |
+
function getPosition( bone, matrixWorldInv ) {
|
261 |
+
|
262 |
+
return _vector
|
263 |
+
.setFromMatrixPosition( bone.matrixWorld )
|
264 |
+
.applyMatrix4( matrixWorldInv );
|
265 |
+
|
266 |
+
}
|
267 |
+
|
268 |
+
function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
|
269 |
+
|
270 |
+
const v = getPosition( bone, matrixWorldInv );
|
271 |
+
|
272 |
+
array[ index * 3 + 0 ] = v.x;
|
273 |
+
array[ index * 3 + 1 ] = v.y;
|
274 |
+
array[ index * 3 + 2 ] = v.z;
|
275 |
+
|
276 |
+
}
|
277 |
+
|
278 |
+
/**
|
279 |
+
* Visualize IK bones
|
280 |
+
*
|
281 |
+
* @param {SkinnedMesh} mesh
|
282 |
+
* @param {Array<Object>} iks
|
283 |
+
* @param {number} sphereSize
|
284 |
+
*/
|
285 |
+
class CCDIKHelper extends Object3D {
|
286 |
+
|
287 |
+
constructor( mesh, iks = [], sphereSize = 0.25 ) {
|
288 |
+
|
289 |
+
super();
|
290 |
+
|
291 |
+
this.root = mesh;
|
292 |
+
this.iks = iks;
|
293 |
+
|
294 |
+
this.matrix.copy( mesh.matrixWorld );
|
295 |
+
this.matrixAutoUpdate = false;
|
296 |
+
|
297 |
+
this.sphereGeometry = new SphereGeometry( sphereSize, 16, 8 );
|
298 |
+
|
299 |
+
this.targetSphereMaterial = new MeshBasicMaterial( {
|
300 |
+
color: new Color( 0xff8888 ),
|
301 |
+
depthTest: false,
|
302 |
+
depthWrite: false,
|
303 |
+
transparent: true
|
304 |
+
} );
|
305 |
+
|
306 |
+
this.effectorSphereMaterial = new MeshBasicMaterial( {
|
307 |
+
color: new Color( 0x88ff88 ),
|
308 |
+
depthTest: false,
|
309 |
+
depthWrite: false,
|
310 |
+
transparent: true
|
311 |
+
} );
|
312 |
+
|
313 |
+
this.linkSphereMaterial = new MeshBasicMaterial( {
|
314 |
+
color: new Color( 0x8888ff ),
|
315 |
+
depthTest: false,
|
316 |
+
depthWrite: false,
|
317 |
+
transparent: true
|
318 |
+
} );
|
319 |
+
|
320 |
+
this.lineMaterial = new LineBasicMaterial( {
|
321 |
+
color: new Color( 0xff0000 ),
|
322 |
+
depthTest: false,
|
323 |
+
depthWrite: false,
|
324 |
+
transparent: true
|
325 |
+
} );
|
326 |
+
|
327 |
+
this._init();
|
328 |
+
|
329 |
+
}
|
330 |
+
|
331 |
+
/**
|
332 |
+
* Updates IK bones visualization.
|
333 |
+
*
|
334 |
+
* @param {Boolean} force
|
335 |
+
*/
|
336 |
+
updateMatrixWorld( force ) {
|
337 |
+
|
338 |
+
const mesh = this.root;
|
339 |
+
|
340 |
+
if ( this.visible ) {
|
341 |
+
|
342 |
+
let offset = 0;
|
343 |
+
|
344 |
+
const iks = this.iks;
|
345 |
+
const bones = mesh.skeleton.bones;
|
346 |
+
|
347 |
+
_matrix.copy( mesh.matrixWorld ).invert();
|
348 |
+
|
349 |
+
for ( let i = 0, il = iks.length; i < il; i ++ ) {
|
350 |
+
|
351 |
+
const ik = iks[ i ];
|
352 |
+
|
353 |
+
const targetBone = bones[ ik.target ];
|
354 |
+
const effectorBone = bones[ ik.effector ];
|
355 |
+
|
356 |
+
const targetMesh = this.children[ offset ++ ];
|
357 |
+
const effectorMesh = this.children[ offset ++ ];
|
358 |
+
|
359 |
+
targetMesh.position.copy( getPosition( targetBone, _matrix ) );
|
360 |
+
effectorMesh.position.copy( getPosition( effectorBone, _matrix ) );
|
361 |
+
|
362 |
+
for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
|
363 |
+
|
364 |
+
const link = ik.links[ j ];
|
365 |
+
const linkBone = bones[ link.index ];
|
366 |
+
|
367 |
+
const linkMesh = this.children[ offset ++ ];
|
368 |
+
|
369 |
+
linkMesh.position.copy( getPosition( linkBone, _matrix ) );
|
370 |
+
|
371 |
+
}
|
372 |
+
|
373 |
+
const line = this.children[ offset ++ ];
|
374 |
+
const array = line.geometry.attributes.position.array;
|
375 |
+
|
376 |
+
setPositionOfBoneToAttributeArray( array, 0, targetBone, _matrix );
|
377 |
+
setPositionOfBoneToAttributeArray( array, 1, effectorBone, _matrix );
|
378 |
+
|
379 |
+
for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
|
380 |
+
|
381 |
+
const link = ik.links[ j ];
|
382 |
+
const linkBone = bones[ link.index ];
|
383 |
+
setPositionOfBoneToAttributeArray( array, j + 2, linkBone, _matrix );
|
384 |
+
|
385 |
+
}
|
386 |
+
|
387 |
+
line.geometry.attributes.position.needsUpdate = true;
|
388 |
+
|
389 |
+
}
|
390 |
+
|
391 |
+
}
|
392 |
+
|
393 |
+
this.matrix.copy( mesh.matrixWorld );
|
394 |
+
|
395 |
+
super.updateMatrixWorld( force );
|
396 |
+
|
397 |
+
}
|
398 |
+
|
399 |
+
/**
|
400 |
+
* Frees the GPU-related resources allocated by this instance. Call this method whenever this instance is no longer used in your app.
|
401 |
+
*/
|
402 |
+
dispose() {
|
403 |
+
|
404 |
+
this.sphereGeometry.dispose();
|
405 |
+
|
406 |
+
this.targetSphereMaterial.dispose();
|
407 |
+
this.effectorSphereMaterial.dispose();
|
408 |
+
this.linkSphereMaterial.dispose();
|
409 |
+
this.lineMaterial.dispose();
|
410 |
+
|
411 |
+
const children = this.children;
|
412 |
+
|
413 |
+
for ( let i = 0; i < children.length; i ++ ) {
|
414 |
+
|
415 |
+
const child = children[ i ];
|
416 |
+
|
417 |
+
if ( child.isLine ) child.geometry.dispose();
|
418 |
+
|
419 |
+
}
|
420 |
+
|
421 |
+
}
|
422 |
+
|
423 |
+
// private method
|
424 |
+
|
425 |
+
_init() {
|
426 |
+
|
427 |
+
const scope = this;
|
428 |
+
const iks = this.iks;
|
429 |
+
|
430 |
+
function createLineGeometry( ik ) {
|
431 |
+
|
432 |
+
const geometry = new BufferGeometry();
|
433 |
+
const vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
|
434 |
+
geometry.setAttribute( 'position', new BufferAttribute( vertices, 3 ) );
|
435 |
+
|
436 |
+
return geometry;
|
437 |
+
|
438 |
+
}
|
439 |
+
|
440 |
+
function createTargetMesh() {
|
441 |
+
|
442 |
+
return new Mesh( scope.sphereGeometry, scope.targetSphereMaterial );
|
443 |
+
|
444 |
+
}
|
445 |
+
|
446 |
+
function createEffectorMesh() {
|
447 |
+
|
448 |
+
return new Mesh( scope.sphereGeometry, scope.effectorSphereMaterial );
|
449 |
+
|
450 |
+
}
|
451 |
+
|
452 |
+
function createLinkMesh() {
|
453 |
+
|
454 |
+
return new Mesh( scope.sphereGeometry, scope.linkSphereMaterial );
|
455 |
+
|
456 |
+
}
|
457 |
+
|
458 |
+
function createLine( ik ) {
|
459 |
+
|
460 |
+
return new Line( createLineGeometry( ik ), scope.lineMaterial );
|
461 |
+
|
462 |
+
}
|
463 |
+
|
464 |
+
for ( let i = 0, il = iks.length; i < il; i ++ ) {
|
465 |
+
|
466 |
+
const ik = iks[ i ];
|
467 |
+
|
468 |
+
this.add( createTargetMesh() );
|
469 |
+
this.add( createEffectorMesh() );
|
470 |
+
|
471 |
+
for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
|
472 |
+
|
473 |
+
this.add( createLinkMesh() );
|
474 |
+
|
475 |
+
}
|
476 |
+
|
477 |
+
this.add( createLine( ik ) );
|
478 |
+
|
479 |
+
}
|
480 |
+
|
481 |
+
}
|
482 |
+
|
483 |
+
}
|
484 |
+
|
485 |
+
export { CCDIKSolver, CCDIKHelper };
|
libs/three.js/0.172.0/jsm/capabilities/WebGL.js
ADDED
@@ -0,0 +1,114 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
class WebGL {
|
2 |
+
|
3 |
+
static isWebGL2Available() {
|
4 |
+
|
5 |
+
try {
|
6 |
+
|
7 |
+
const canvas = document.createElement( 'canvas' );
|
8 |
+
return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
|
9 |
+
|
10 |
+
} catch ( e ) {
|
11 |
+
|
12 |
+
return false;
|
13 |
+
|
14 |
+
}
|
15 |
+
|
16 |
+
}
|
17 |
+
|
18 |
+
static isColorSpaceAvailable( colorSpace ) {
|
19 |
+
|
20 |
+
try {
|
21 |
+
|
22 |
+
const canvas = document.createElement( 'canvas' );
|
23 |
+
const ctx = window.WebGL2RenderingContext && canvas.getContext( 'webgl2' );
|
24 |
+
ctx.drawingBufferColorSpace = colorSpace;
|
25 |
+
return ctx.drawingBufferColorSpace === colorSpace; // deepscan-disable-line SAME_OPERAND_VALUE
|
26 |
+
|
27 |
+
} catch ( e ) {
|
28 |
+
|
29 |
+
return false;
|
30 |
+
|
31 |
+
}
|
32 |
+
|
33 |
+
}
|
34 |
+
|
35 |
+
static getWebGL2ErrorMessage() {
|
36 |
+
|
37 |
+
return this.getErrorMessage( 2 );
|
38 |
+
|
39 |
+
}
|
40 |
+
|
41 |
+
static getErrorMessage( version ) {
|
42 |
+
|
43 |
+
const names = {
|
44 |
+
1: 'WebGL',
|
45 |
+
2: 'WebGL 2'
|
46 |
+
};
|
47 |
+
|
48 |
+
const contexts = {
|
49 |
+
1: window.WebGLRenderingContext,
|
50 |
+
2: window.WebGL2RenderingContext
|
51 |
+
};
|
52 |
+
|
53 |
+
let message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
|
54 |
+
|
55 |
+
const element = document.createElement( 'div' );
|
56 |
+
element.id = 'webglmessage';
|
57 |
+
element.style.fontFamily = 'monospace';
|
58 |
+
element.style.fontSize = '13px';
|
59 |
+
element.style.fontWeight = 'normal';
|
60 |
+
element.style.textAlign = 'center';
|
61 |
+
element.style.background = '#fff';
|
62 |
+
element.style.color = '#000';
|
63 |
+
element.style.padding = '1.5em';
|
64 |
+
element.style.width = '400px';
|
65 |
+
element.style.margin = '5em auto 0';
|
66 |
+
|
67 |
+
if ( contexts[ version ] ) {
|
68 |
+
|
69 |
+
message = message.replace( '$0', 'graphics card' );
|
70 |
+
|
71 |
+
} else {
|
72 |
+
|
73 |
+
message = message.replace( '$0', 'browser' );
|
74 |
+
|
75 |
+
}
|
76 |
+
|
77 |
+
message = message.replace( '$1', names[ version ] );
|
78 |
+
|
79 |
+
element.innerHTML = message;
|
80 |
+
|
81 |
+
return element;
|
82 |
+
|
83 |
+
}
|
84 |
+
|
85 |
+
// @deprecated, r168
|
86 |
+
|
87 |
+
static isWebGLAvailable() {
|
88 |
+
|
89 |
+
console.warn( 'isWebGLAvailable() has been deprecated and will be removed in r178. Use isWebGL2Available() instead.' );
|
90 |
+
|
91 |
+
try {
|
92 |
+
|
93 |
+
const canvas = document.createElement( 'canvas' );
|
94 |
+
return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
|
95 |
+
|
96 |
+
} catch ( e ) {
|
97 |
+
|
98 |
+
return false;
|
99 |
+
|
100 |
+
}
|
101 |
+
|
102 |
+
}
|
103 |
+
|
104 |
+
static getWebGLErrorMessage() {
|
105 |
+
|
106 |
+
console.warn( 'getWebGLErrorMessage() has been deprecated and will be removed in r178. Use getWebGL2ErrorMessage() instead.' );
|
107 |
+
|
108 |
+
return this.getErrorMessage( 1 );
|
109 |
+
|
110 |
+
}
|
111 |
+
|
112 |
+
}
|
113 |
+
|
114 |
+
export default WebGL;
|
libs/three.js/0.172.0/jsm/capabilities/WebGPU.js
ADDED
@@ -0,0 +1,48 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
let isAvailable = ( typeof navigator !== 'undefined' && navigator.gpu !== undefined );
|
2 |
+
|
3 |
+
if ( typeof window !== 'undefined' && isAvailable ) {
|
4 |
+
|
5 |
+
isAvailable = await navigator.gpu.requestAdapter();
|
6 |
+
|
7 |
+
}
|
8 |
+
|
9 |
+
class WebGPU {
|
10 |
+
|
11 |
+
static isAvailable() {
|
12 |
+
|
13 |
+
return Boolean( isAvailable );
|
14 |
+
|
15 |
+
}
|
16 |
+
|
17 |
+
static getStaticAdapter() {
|
18 |
+
|
19 |
+
return isAvailable;
|
20 |
+
|
21 |
+
}
|
22 |
+
|
23 |
+
static getErrorMessage() {
|
24 |
+
|
25 |
+
const message = 'Your browser does not support <a href="https://gpuweb.github.io/gpuweb/" style="color:blue">WebGPU</a> yet';
|
26 |
+
|
27 |
+
const element = document.createElement( 'div' );
|
28 |
+
element.id = 'webgpumessage';
|
29 |
+
element.style.fontFamily = 'monospace';
|
30 |
+
element.style.fontSize = '13px';
|
31 |
+
element.style.fontWeight = 'normal';
|
32 |
+
element.style.textAlign = 'center';
|
33 |
+
element.style.background = '#fff';
|
34 |
+
element.style.color = '#000';
|
35 |
+
element.style.padding = '1.5em';
|
36 |
+
element.style.maxWidth = '400px';
|
37 |
+
element.style.margin = '5em auto 0';
|
38 |
+
|
39 |
+
element.innerHTML = message;
|
40 |
+
|
41 |
+
return element;
|
42 |
+
|
43 |
+
}
|
44 |
+
|
45 |
+
}
|
46 |
+
|
47 |
+
|
48 |
+
export default WebGPU;
|
libs/three.js/0.172.0/jsm/controls/ArcballControls.js
ADDED
@@ -0,0 +1,3253 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
GridHelper,
|
4 |
+
EllipseCurve,
|
5 |
+
BufferGeometry,
|
6 |
+
Line,
|
7 |
+
LineBasicMaterial,
|
8 |
+
Raycaster,
|
9 |
+
Group,
|
10 |
+
Box3,
|
11 |
+
Sphere,
|
12 |
+
Quaternion,
|
13 |
+
Vector2,
|
14 |
+
Vector3,
|
15 |
+
Matrix4,
|
16 |
+
MathUtils
|
17 |
+
} from 'three';
|
18 |
+
|
19 |
+
//trackball state
|
20 |
+
const STATE = {
|
21 |
+
|
22 |
+
IDLE: Symbol(),
|
23 |
+
ROTATE: Symbol(),
|
24 |
+
PAN: Symbol(),
|
25 |
+
SCALE: Symbol(),
|
26 |
+
FOV: Symbol(),
|
27 |
+
FOCUS: Symbol(),
|
28 |
+
ZROTATE: Symbol(),
|
29 |
+
TOUCH_MULTI: Symbol(),
|
30 |
+
ANIMATION_FOCUS: Symbol(),
|
31 |
+
ANIMATION_ROTATE: Symbol()
|
32 |
+
|
33 |
+
};
|
34 |
+
|
35 |
+
const INPUT = {
|
36 |
+
|
37 |
+
NONE: Symbol(),
|
38 |
+
ONE_FINGER: Symbol(),
|
39 |
+
ONE_FINGER_SWITCHED: Symbol(),
|
40 |
+
TWO_FINGER: Symbol(),
|
41 |
+
MULT_FINGER: Symbol(),
|
42 |
+
CURSOR: Symbol()
|
43 |
+
|
44 |
+
};
|
45 |
+
|
46 |
+
//cursor center coordinates
|
47 |
+
const _center = {
|
48 |
+
|
49 |
+
x: 0,
|
50 |
+
y: 0
|
51 |
+
|
52 |
+
};
|
53 |
+
|
54 |
+
//transformation matrices for gizmos and camera
|
55 |
+
const _transformation = {
|
56 |
+
|
57 |
+
camera: new Matrix4(),
|
58 |
+
gizmos: new Matrix4()
|
59 |
+
|
60 |
+
};
|
61 |
+
|
62 |
+
//events
|
63 |
+
const _changeEvent = { type: 'change' };
|
64 |
+
const _startEvent = { type: 'start' };
|
65 |
+
const _endEvent = { type: 'end' };
|
66 |
+
|
67 |
+
const _raycaster = new Raycaster();
|
68 |
+
const _offset = new Vector3();
|
69 |
+
|
70 |
+
const _gizmoMatrixStateTemp = new Matrix4();
|
71 |
+
const _cameraMatrixStateTemp = new Matrix4();
|
72 |
+
const _scalePointTemp = new Vector3();
|
73 |
+
/**
|
74 |
+
*
|
75 |
+
* @param {Camera} camera Virtual camera used in the scene
|
76 |
+
* @param {HTMLElement} domElement Renderer's dom element
|
77 |
+
* @param {Scene} scene The scene to be rendered
|
78 |
+
*/
|
79 |
+
class ArcballControls extends Controls {
|
80 |
+
|
81 |
+
constructor( camera, domElement = null, scene = null ) {
|
82 |
+
|
83 |
+
super( camera, domElement );
|
84 |
+
|
85 |
+
this.scene = scene;
|
86 |
+
this.target = new Vector3();
|
87 |
+
this._currentTarget = new Vector3();
|
88 |
+
this.radiusFactor = 0.67;
|
89 |
+
|
90 |
+
this.mouseActions = [];
|
91 |
+
this._mouseOp = null;
|
92 |
+
|
93 |
+
|
94 |
+
//global vectors and matrices that are used in some operations to avoid creating new objects every time (e.g. every time cursor moves)
|
95 |
+
this._v2_1 = new Vector2();
|
96 |
+
this._v3_1 = new Vector3();
|
97 |
+
this._v3_2 = new Vector3();
|
98 |
+
|
99 |
+
this._m4_1 = new Matrix4();
|
100 |
+
this._m4_2 = new Matrix4();
|
101 |
+
|
102 |
+
this._quat = new Quaternion();
|
103 |
+
|
104 |
+
//transformation matrices
|
105 |
+
this._translationMatrix = new Matrix4(); //matrix for translation operation
|
106 |
+
this._rotationMatrix = new Matrix4(); //matrix for rotation operation
|
107 |
+
this._scaleMatrix = new Matrix4(); //matrix for scaling operation
|
108 |
+
|
109 |
+
this._rotationAxis = new Vector3(); //axis for rotate operation
|
110 |
+
|
111 |
+
|
112 |
+
//camera state
|
113 |
+
this._cameraMatrixState = new Matrix4();
|
114 |
+
this._cameraProjectionState = new Matrix4();
|
115 |
+
|
116 |
+
this._fovState = 1;
|
117 |
+
this._upState = new Vector3();
|
118 |
+
this._zoomState = 1;
|
119 |
+
this._nearPos = 0;
|
120 |
+
this._farPos = 0;
|
121 |
+
|
122 |
+
this._gizmoMatrixState = new Matrix4();
|
123 |
+
|
124 |
+
//initial values
|
125 |
+
this._up0 = new Vector3();
|
126 |
+
this._zoom0 = 1;
|
127 |
+
this._fov0 = 0;
|
128 |
+
this._initialNear = 0;
|
129 |
+
this._nearPos0 = 0;
|
130 |
+
this._initialFar = 0;
|
131 |
+
this._farPos0 = 0;
|
132 |
+
this._cameraMatrixState0 = new Matrix4();
|
133 |
+
this._gizmoMatrixState0 = new Matrix4();
|
134 |
+
|
135 |
+
//pointers array
|
136 |
+
this._button = - 1;
|
137 |
+
this._touchStart = [];
|
138 |
+
this._touchCurrent = [];
|
139 |
+
this._input = INPUT.NONE;
|
140 |
+
|
141 |
+
//two fingers touch interaction
|
142 |
+
this._switchSensibility = 32; //minimum movement to be performed to fire single pan start after the second finger has been released
|
143 |
+
this._startFingerDistance = 0; //distance between two fingers
|
144 |
+
this._currentFingerDistance = 0;
|
145 |
+
this._startFingerRotation = 0; //amount of rotation performed with two fingers
|
146 |
+
this._currentFingerRotation = 0;
|
147 |
+
|
148 |
+
//double tap
|
149 |
+
this._devPxRatio = 0;
|
150 |
+
this._downValid = true;
|
151 |
+
this._nclicks = 0;
|
152 |
+
this._downEvents = [];
|
153 |
+
this._downStart = 0; //pointerDown time
|
154 |
+
this._clickStart = 0; //first click time
|
155 |
+
this._maxDownTime = 250;
|
156 |
+
this._maxInterval = 300;
|
157 |
+
this._posThreshold = 24;
|
158 |
+
this._movementThreshold = 24;
|
159 |
+
|
160 |
+
//cursor positions
|
161 |
+
this._currentCursorPosition = new Vector3();
|
162 |
+
this._startCursorPosition = new Vector3();
|
163 |
+
|
164 |
+
//grid
|
165 |
+
this._grid = null; //grid to be visualized during pan operation
|
166 |
+
this._gridPosition = new Vector3();
|
167 |
+
|
168 |
+
//gizmos
|
169 |
+
this._gizmos = new Group();
|
170 |
+
this._curvePts = 128;
|
171 |
+
|
172 |
+
|
173 |
+
//animations
|
174 |
+
this._timeStart = - 1; //initial time
|
175 |
+
this._animationId = - 1;
|
176 |
+
|
177 |
+
//focus animation
|
178 |
+
this.focusAnimationTime = 500; //duration of focus animation in ms
|
179 |
+
|
180 |
+
//rotate animation
|
181 |
+
this._timePrev = 0; //time at which previous rotate operation has been detected
|
182 |
+
this._timeCurrent = 0; //time at which current rotate operation has been detected
|
183 |
+
this._anglePrev = 0; //angle of previous rotation
|
184 |
+
this._angleCurrent = 0; //angle of current rotation
|
185 |
+
this._cursorPosPrev = new Vector3(); //cursor position when previous rotate operation has been detected
|
186 |
+
this._cursorPosCurr = new Vector3();//cursor position when current rotate operation has been detected
|
187 |
+
this._wPrev = 0; //angular velocity of the previous rotate operation
|
188 |
+
this._wCurr = 0; //angular velocity of the current rotate operation
|
189 |
+
|
190 |
+
|
191 |
+
//parameters
|
192 |
+
this.adjustNearFar = false;
|
193 |
+
this.scaleFactor = 1.1; //zoom/distance multiplier
|
194 |
+
this.dampingFactor = 25;
|
195 |
+
this.wMax = 20; //maximum angular velocity allowed
|
196 |
+
this.enableAnimations = true; //if animations should be performed
|
197 |
+
this.enableGrid = false; //if grid should be showed during pan operation
|
198 |
+
this.cursorZoom = false; //if wheel zoom should be cursor centered
|
199 |
+
this.minFov = 5;
|
200 |
+
this.maxFov = 90;
|
201 |
+
this.rotateSpeed = 1;
|
202 |
+
|
203 |
+
this.enablePan = true;
|
204 |
+
this.enableRotate = true;
|
205 |
+
this.enableZoom = true;
|
206 |
+
this.enableGizmos = true;
|
207 |
+
this.enableFocus = true;
|
208 |
+
|
209 |
+
this.minDistance = 0;
|
210 |
+
this.maxDistance = Infinity;
|
211 |
+
this.minZoom = 0;
|
212 |
+
this.maxZoom = Infinity;
|
213 |
+
|
214 |
+
//trackball parameters
|
215 |
+
this._tbRadius = 1;
|
216 |
+
|
217 |
+
//FSA
|
218 |
+
this._state = STATE.IDLE;
|
219 |
+
|
220 |
+
this.setCamera( camera );
|
221 |
+
|
222 |
+
if ( this.scene != null ) {
|
223 |
+
|
224 |
+
this.scene.add( this._gizmos );
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
this.initializeMouseActions();
|
229 |
+
|
230 |
+
// event listeners
|
231 |
+
|
232 |
+
this._onContextMenu = onContextMenu.bind( this );
|
233 |
+
this._onWheel = onWheel.bind( this );
|
234 |
+
this._onPointerUp = onPointerUp.bind( this );
|
235 |
+
this._onPointerMove = onPointerMove.bind( this );
|
236 |
+
this._onPointerDown = onPointerDown.bind( this );
|
237 |
+
this._onPointerCancel = onPointerCancel.bind( this );
|
238 |
+
this._onWindowResize = onWindowResize.bind( this );
|
239 |
+
|
240 |
+
if ( domElement !== null ) {
|
241 |
+
|
242 |
+
this.connect();
|
243 |
+
|
244 |
+
}
|
245 |
+
|
246 |
+
}
|
247 |
+
|
248 |
+
connect() {
|
249 |
+
|
250 |
+
this.domElement.style.touchAction = 'none';
|
251 |
+
this._devPxRatio = window.devicePixelRatio;
|
252 |
+
|
253 |
+
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
254 |
+
this.domElement.addEventListener( 'wheel', this._onWheel );
|
255 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
256 |
+
this.domElement.addEventListener( 'pointercancel', this._onPointerCancel );
|
257 |
+
|
258 |
+
window.addEventListener( 'resize', this._onWindowResize );
|
259 |
+
|
260 |
+
}
|
261 |
+
|
262 |
+
disconnect() {
|
263 |
+
|
264 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
265 |
+
this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel );
|
266 |
+
this.domElement.removeEventListener( 'wheel', this._onWheel );
|
267 |
+
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
268 |
+
|
269 |
+
window.removeEventListener( 'pointermove', this._onPointerMove );
|
270 |
+
window.removeEventListener( 'pointerup', this._onPointerUp );
|
271 |
+
|
272 |
+
window.removeEventListener( 'resize', this._onWindowResize );
|
273 |
+
|
274 |
+
}
|
275 |
+
|
276 |
+
onSinglePanStart( event, operation ) {
|
277 |
+
|
278 |
+
if ( this.enabled ) {
|
279 |
+
|
280 |
+
this.dispatchEvent( _startEvent );
|
281 |
+
|
282 |
+
this.setCenter( event.clientX, event.clientY );
|
283 |
+
|
284 |
+
switch ( operation ) {
|
285 |
+
|
286 |
+
case 'PAN':
|
287 |
+
|
288 |
+
if ( ! this.enablePan ) {
|
289 |
+
|
290 |
+
return;
|
291 |
+
|
292 |
+
}
|
293 |
+
|
294 |
+
if ( this._animationId != - 1 ) {
|
295 |
+
|
296 |
+
cancelAnimationFrame( this._animationId );
|
297 |
+
this._animationId = - 1;
|
298 |
+
this._timeStart = - 1;
|
299 |
+
|
300 |
+
this.activateGizmos( false );
|
301 |
+
this.dispatchEvent( _changeEvent );
|
302 |
+
|
303 |
+
}
|
304 |
+
|
305 |
+
this.updateTbState( STATE.PAN, true );
|
306 |
+
this._startCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) );
|
307 |
+
if ( this.enableGrid ) {
|
308 |
+
|
309 |
+
this.drawGrid();
|
310 |
+
this.dispatchEvent( _changeEvent );
|
311 |
+
|
312 |
+
}
|
313 |
+
|
314 |
+
break;
|
315 |
+
|
316 |
+
case 'ROTATE':
|
317 |
+
|
318 |
+
if ( ! this.enableRotate ) {
|
319 |
+
|
320 |
+
return;
|
321 |
+
|
322 |
+
}
|
323 |
+
|
324 |
+
if ( this._animationId != - 1 ) {
|
325 |
+
|
326 |
+
cancelAnimationFrame( this._animationId );
|
327 |
+
this._animationId = - 1;
|
328 |
+
this._timeStart = - 1;
|
329 |
+
|
330 |
+
}
|
331 |
+
|
332 |
+
this.updateTbState( STATE.ROTATE, true );
|
333 |
+
this._startCursorPosition.copy( this.unprojectOnTbSurface( this.object, _center.x, _center.y, this.domElement, this._tbRadius ) );
|
334 |
+
this.activateGizmos( true );
|
335 |
+
if ( this.enableAnimations ) {
|
336 |
+
|
337 |
+
this._timePrev = this._timeCurrent = performance.now();
|
338 |
+
this._angleCurrent = this._anglePrev = 0;
|
339 |
+
this._cursorPosPrev.copy( this._startCursorPosition );
|
340 |
+
this._cursorPosCurr.copy( this._cursorPosPrev );
|
341 |
+
this._wCurr = 0;
|
342 |
+
this._wPrev = this._wCurr;
|
343 |
+
|
344 |
+
}
|
345 |
+
|
346 |
+
this.dispatchEvent( _changeEvent );
|
347 |
+
break;
|
348 |
+
|
349 |
+
case 'FOV':
|
350 |
+
|
351 |
+
if ( ! this.object.isPerspectiveCamera || ! this.enableZoom ) {
|
352 |
+
|
353 |
+
return;
|
354 |
+
|
355 |
+
}
|
356 |
+
|
357 |
+
if ( this._animationId != - 1 ) {
|
358 |
+
|
359 |
+
cancelAnimationFrame( this._animationId );
|
360 |
+
this._animationId = - 1;
|
361 |
+
this._timeStart = - 1;
|
362 |
+
|
363 |
+
this.activateGizmos( false );
|
364 |
+
this.dispatchEvent( _changeEvent );
|
365 |
+
|
366 |
+
}
|
367 |
+
|
368 |
+
this.updateTbState( STATE.FOV, true );
|
369 |
+
this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
370 |
+
this._currentCursorPosition.copy( this._startCursorPosition );
|
371 |
+
break;
|
372 |
+
|
373 |
+
case 'ZOOM':
|
374 |
+
|
375 |
+
if ( ! this.enableZoom ) {
|
376 |
+
|
377 |
+
return;
|
378 |
+
|
379 |
+
}
|
380 |
+
|
381 |
+
if ( this._animationId != - 1 ) {
|
382 |
+
|
383 |
+
cancelAnimationFrame( this._animationId );
|
384 |
+
this._animationId = - 1;
|
385 |
+
this._timeStart = - 1;
|
386 |
+
|
387 |
+
this.activateGizmos( false );
|
388 |
+
this.dispatchEvent( _changeEvent );
|
389 |
+
|
390 |
+
}
|
391 |
+
|
392 |
+
this.updateTbState( STATE.SCALE, true );
|
393 |
+
this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
394 |
+
this._currentCursorPosition.copy( this._startCursorPosition );
|
395 |
+
break;
|
396 |
+
|
397 |
+
}
|
398 |
+
|
399 |
+
}
|
400 |
+
|
401 |
+
}
|
402 |
+
|
403 |
+
onSinglePanMove( event, opState ) {
|
404 |
+
|
405 |
+
if ( this.enabled ) {
|
406 |
+
|
407 |
+
const restart = opState != this._state;
|
408 |
+
this.setCenter( event.clientX, event.clientY );
|
409 |
+
|
410 |
+
switch ( opState ) {
|
411 |
+
|
412 |
+
case STATE.PAN:
|
413 |
+
|
414 |
+
if ( this.enablePan ) {
|
415 |
+
|
416 |
+
if ( restart ) {
|
417 |
+
|
418 |
+
//switch to pan operation
|
419 |
+
|
420 |
+
this.dispatchEvent( _endEvent );
|
421 |
+
this.dispatchEvent( _startEvent );
|
422 |
+
|
423 |
+
this.updateTbState( opState, true );
|
424 |
+
this._startCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) );
|
425 |
+
if ( this.enableGrid ) {
|
426 |
+
|
427 |
+
this.drawGrid();
|
428 |
+
|
429 |
+
}
|
430 |
+
|
431 |
+
this.activateGizmos( false );
|
432 |
+
|
433 |
+
} else {
|
434 |
+
|
435 |
+
//continue with pan operation
|
436 |
+
this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ) );
|
437 |
+
this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition ) );
|
438 |
+
|
439 |
+
}
|
440 |
+
|
441 |
+
}
|
442 |
+
|
443 |
+
break;
|
444 |
+
|
445 |
+
case STATE.ROTATE:
|
446 |
+
|
447 |
+
if ( this.enableRotate ) {
|
448 |
+
|
449 |
+
if ( restart ) {
|
450 |
+
|
451 |
+
//switch to rotate operation
|
452 |
+
|
453 |
+
this.dispatchEvent( _endEvent );
|
454 |
+
this.dispatchEvent( _startEvent );
|
455 |
+
|
456 |
+
this.updateTbState( opState, true );
|
457 |
+
this._startCursorPosition.copy( this.unprojectOnTbSurface( this.object, _center.x, _center.y, this.domElement, this._tbRadius ) );
|
458 |
+
|
459 |
+
if ( this.enableGrid ) {
|
460 |
+
|
461 |
+
this.disposeGrid();
|
462 |
+
|
463 |
+
}
|
464 |
+
|
465 |
+
this.activateGizmos( true );
|
466 |
+
|
467 |
+
} else {
|
468 |
+
|
469 |
+
//continue with rotate operation
|
470 |
+
this._currentCursorPosition.copy( this.unprojectOnTbSurface( this.object, _center.x, _center.y, this.domElement, this._tbRadius ) );
|
471 |
+
|
472 |
+
const distance = this._startCursorPosition.distanceTo( this._currentCursorPosition );
|
473 |
+
const angle = this._startCursorPosition.angleTo( this._currentCursorPosition );
|
474 |
+
const amount = Math.max( distance / this._tbRadius, angle ) * this.rotateSpeed; //effective rotation angle
|
475 |
+
|
476 |
+
this.applyTransformMatrix( this.rotate( this.calculateRotationAxis( this._startCursorPosition, this._currentCursorPosition ), amount ) );
|
477 |
+
|
478 |
+
if ( this.enableAnimations ) {
|
479 |
+
|
480 |
+
this._timePrev = this._timeCurrent;
|
481 |
+
this._timeCurrent = performance.now();
|
482 |
+
this._anglePrev = this._angleCurrent;
|
483 |
+
this._angleCurrent = amount;
|
484 |
+
this._cursorPosPrev.copy( this._cursorPosCurr );
|
485 |
+
this._cursorPosCurr.copy( this._currentCursorPosition );
|
486 |
+
this._wPrev = this._wCurr;
|
487 |
+
this._wCurr = this.calculateAngularSpeed( this._anglePrev, this._angleCurrent, this._timePrev, this._timeCurrent );
|
488 |
+
|
489 |
+
}
|
490 |
+
|
491 |
+
}
|
492 |
+
|
493 |
+
}
|
494 |
+
|
495 |
+
break;
|
496 |
+
|
497 |
+
case STATE.SCALE:
|
498 |
+
|
499 |
+
if ( this.enableZoom ) {
|
500 |
+
|
501 |
+
if ( restart ) {
|
502 |
+
|
503 |
+
//switch to zoom operation
|
504 |
+
|
505 |
+
this.dispatchEvent( _endEvent );
|
506 |
+
this.dispatchEvent( _startEvent );
|
507 |
+
|
508 |
+
this.updateTbState( opState, true );
|
509 |
+
this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
510 |
+
this._currentCursorPosition.copy( this._startCursorPosition );
|
511 |
+
|
512 |
+
if ( this.enableGrid ) {
|
513 |
+
|
514 |
+
this.disposeGrid();
|
515 |
+
|
516 |
+
}
|
517 |
+
|
518 |
+
this.activateGizmos( false );
|
519 |
+
|
520 |
+
} else {
|
521 |
+
|
522 |
+
//continue with zoom operation
|
523 |
+
const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
|
524 |
+
this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
525 |
+
|
526 |
+
const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
|
527 |
+
|
528 |
+
let size = 1;
|
529 |
+
|
530 |
+
if ( movement < 0 ) {
|
531 |
+
|
532 |
+
size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
|
533 |
+
|
534 |
+
} else if ( movement > 0 ) {
|
535 |
+
|
536 |
+
size = Math.pow( this.scaleFactor, movement * screenNotches );
|
537 |
+
|
538 |
+
}
|
539 |
+
|
540 |
+
this._v3_1.setFromMatrixPosition( this._gizmoMatrixState );
|
541 |
+
|
542 |
+
this.applyTransformMatrix( this.scale( size, this._v3_1 ) );
|
543 |
+
|
544 |
+
}
|
545 |
+
|
546 |
+
}
|
547 |
+
|
548 |
+
break;
|
549 |
+
|
550 |
+
case STATE.FOV:
|
551 |
+
|
552 |
+
if ( this.enableZoom && this.object.isPerspectiveCamera ) {
|
553 |
+
|
554 |
+
if ( restart ) {
|
555 |
+
|
556 |
+
//switch to fov operation
|
557 |
+
|
558 |
+
this.dispatchEvent( _endEvent );
|
559 |
+
this.dispatchEvent( _startEvent );
|
560 |
+
|
561 |
+
this.updateTbState( opState, true );
|
562 |
+
this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
563 |
+
this._currentCursorPosition.copy( this._startCursorPosition );
|
564 |
+
|
565 |
+
if ( this.enableGrid ) {
|
566 |
+
|
567 |
+
this.disposeGrid();
|
568 |
+
|
569 |
+
}
|
570 |
+
|
571 |
+
this.activateGizmos( false );
|
572 |
+
|
573 |
+
} else {
|
574 |
+
|
575 |
+
//continue with fov operation
|
576 |
+
const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
|
577 |
+
this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
578 |
+
|
579 |
+
const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
|
580 |
+
|
581 |
+
let size = 1;
|
582 |
+
|
583 |
+
if ( movement < 0 ) {
|
584 |
+
|
585 |
+
size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
|
586 |
+
|
587 |
+
} else if ( movement > 0 ) {
|
588 |
+
|
589 |
+
size = Math.pow( this.scaleFactor, movement * screenNotches );
|
590 |
+
|
591 |
+
}
|
592 |
+
|
593 |
+
this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
|
594 |
+
const x = this._v3_1.distanceTo( this._gizmos.position );
|
595 |
+
let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
|
596 |
+
|
597 |
+
//check min and max distance
|
598 |
+
xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
|
599 |
+
|
600 |
+
const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 );
|
601 |
+
|
602 |
+
//calculate new fov
|
603 |
+
let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
|
604 |
+
|
605 |
+
//check min and max fov
|
606 |
+
newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov );
|
607 |
+
|
608 |
+
const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
|
609 |
+
size = x / newDistance;
|
610 |
+
this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
|
611 |
+
|
612 |
+
this.setFov( newFov );
|
613 |
+
this.applyTransformMatrix( this.scale( size, this._v3_2, false ) );
|
614 |
+
|
615 |
+
//adjusting distance
|
616 |
+
_offset.copy( this._gizmos.position ).sub( this.object.position ).normalize().multiplyScalar( newDistance / x );
|
617 |
+
this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
|
618 |
+
|
619 |
+
}
|
620 |
+
|
621 |
+
}
|
622 |
+
|
623 |
+
break;
|
624 |
+
|
625 |
+
}
|
626 |
+
|
627 |
+
this.dispatchEvent( _changeEvent );
|
628 |
+
|
629 |
+
}
|
630 |
+
|
631 |
+
}
|
632 |
+
|
633 |
+
onSinglePanEnd() {
|
634 |
+
|
635 |
+
if ( this._state == STATE.ROTATE ) {
|
636 |
+
|
637 |
+
|
638 |
+
if ( ! this.enableRotate ) {
|
639 |
+
|
640 |
+
return;
|
641 |
+
|
642 |
+
}
|
643 |
+
|
644 |
+
if ( this.enableAnimations ) {
|
645 |
+
|
646 |
+
//perform rotation animation
|
647 |
+
const deltaTime = ( performance.now() - this._timeCurrent );
|
648 |
+
if ( deltaTime < 120 ) {
|
649 |
+
|
650 |
+
const w = Math.abs( ( this._wPrev + this._wCurr ) / 2 );
|
651 |
+
|
652 |
+
const self = this;
|
653 |
+
this._animationId = window.requestAnimationFrame( function ( t ) {
|
654 |
+
|
655 |
+
self.updateTbState( STATE.ANIMATION_ROTATE, true );
|
656 |
+
const rotationAxis = self.calculateRotationAxis( self._cursorPosPrev, self._cursorPosCurr );
|
657 |
+
|
658 |
+
self.onRotationAnim( t, rotationAxis, Math.min( w, self.wMax ) );
|
659 |
+
|
660 |
+
} );
|
661 |
+
|
662 |
+
} else {
|
663 |
+
|
664 |
+
//cursor has been standing still for over 120 ms since last movement
|
665 |
+
this.updateTbState( STATE.IDLE, false );
|
666 |
+
this.activateGizmos( false );
|
667 |
+
this.dispatchEvent( _changeEvent );
|
668 |
+
|
669 |
+
}
|
670 |
+
|
671 |
+
} else {
|
672 |
+
|
673 |
+
this.updateTbState( STATE.IDLE, false );
|
674 |
+
this.activateGizmos( false );
|
675 |
+
this.dispatchEvent( _changeEvent );
|
676 |
+
|
677 |
+
}
|
678 |
+
|
679 |
+
} else if ( this._state == STATE.PAN || this._state == STATE.IDLE ) {
|
680 |
+
|
681 |
+
this.updateTbState( STATE.IDLE, false );
|
682 |
+
|
683 |
+
if ( this.enableGrid ) {
|
684 |
+
|
685 |
+
this.disposeGrid();
|
686 |
+
|
687 |
+
}
|
688 |
+
|
689 |
+
this.activateGizmos( false );
|
690 |
+
this.dispatchEvent( _changeEvent );
|
691 |
+
|
692 |
+
|
693 |
+
}
|
694 |
+
|
695 |
+
this.dispatchEvent( _endEvent );
|
696 |
+
|
697 |
+
}
|
698 |
+
|
699 |
+
onDoubleTap( event ) {
|
700 |
+
|
701 |
+
if ( this.enabled && this.enablePan && this.enableFocus && this.scene != null ) {
|
702 |
+
|
703 |
+
this.dispatchEvent( _startEvent );
|
704 |
+
|
705 |
+
this.setCenter( event.clientX, event.clientY );
|
706 |
+
const hitP = this.unprojectOnObj( this.getCursorNDC( _center.x, _center.y, this.domElement ), this.object );
|
707 |
+
|
708 |
+
if ( hitP != null && this.enableAnimations ) {
|
709 |
+
|
710 |
+
const self = this;
|
711 |
+
if ( this._animationId != - 1 ) {
|
712 |
+
|
713 |
+
window.cancelAnimationFrame( this._animationId );
|
714 |
+
|
715 |
+
}
|
716 |
+
|
717 |
+
this._timeStart = - 1;
|
718 |
+
this._animationId = window.requestAnimationFrame( function ( t ) {
|
719 |
+
|
720 |
+
self.updateTbState( STATE.ANIMATION_FOCUS, true );
|
721 |
+
self.onFocusAnim( t, hitP, self._cameraMatrixState, self._gizmoMatrixState );
|
722 |
+
|
723 |
+
} );
|
724 |
+
|
725 |
+
} else if ( hitP != null && ! this.enableAnimations ) {
|
726 |
+
|
727 |
+
this.updateTbState( STATE.FOCUS, true );
|
728 |
+
this.focus( hitP, this.scaleFactor );
|
729 |
+
this.updateTbState( STATE.IDLE, false );
|
730 |
+
this.dispatchEvent( _changeEvent );
|
731 |
+
|
732 |
+
}
|
733 |
+
|
734 |
+
}
|
735 |
+
|
736 |
+
this.dispatchEvent( _endEvent );
|
737 |
+
|
738 |
+
}
|
739 |
+
|
740 |
+
onDoublePanStart() {
|
741 |
+
|
742 |
+
if ( this.enabled && this.enablePan ) {
|
743 |
+
|
744 |
+
this.dispatchEvent( _startEvent );
|
745 |
+
|
746 |
+
this.updateTbState( STATE.PAN, true );
|
747 |
+
|
748 |
+
this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
|
749 |
+
this._startCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement, true ) );
|
750 |
+
this._currentCursorPosition.copy( this._startCursorPosition );
|
751 |
+
|
752 |
+
this.activateGizmos( false );
|
753 |
+
|
754 |
+
}
|
755 |
+
|
756 |
+
}
|
757 |
+
|
758 |
+
onDoublePanMove() {
|
759 |
+
|
760 |
+
if ( this.enabled && this.enablePan ) {
|
761 |
+
|
762 |
+
this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
|
763 |
+
|
764 |
+
if ( this._state != STATE.PAN ) {
|
765 |
+
|
766 |
+
this.updateTbState( STATE.PAN, true );
|
767 |
+
this._startCursorPosition.copy( this._currentCursorPosition );
|
768 |
+
|
769 |
+
}
|
770 |
+
|
771 |
+
this._currentCursorPosition.copy( this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement, true ) );
|
772 |
+
this.applyTransformMatrix( this.pan( this._startCursorPosition, this._currentCursorPosition, true ) );
|
773 |
+
this.dispatchEvent( _changeEvent );
|
774 |
+
|
775 |
+
}
|
776 |
+
|
777 |
+
}
|
778 |
+
|
779 |
+
onDoublePanEnd() {
|
780 |
+
|
781 |
+
this.updateTbState( STATE.IDLE, false );
|
782 |
+
this.dispatchEvent( _endEvent );
|
783 |
+
|
784 |
+
}
|
785 |
+
|
786 |
+
onRotateStart() {
|
787 |
+
|
788 |
+
if ( this.enabled && this.enableRotate ) {
|
789 |
+
|
790 |
+
this.dispatchEvent( _startEvent );
|
791 |
+
|
792 |
+
this.updateTbState( STATE.ZROTATE, true );
|
793 |
+
|
794 |
+
//this._startFingerRotation = event.rotation;
|
795 |
+
|
796 |
+
this._startFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
|
797 |
+
this._currentFingerRotation = this._startFingerRotation;
|
798 |
+
|
799 |
+
this.object.getWorldDirection( this._rotationAxis ); //rotation axis
|
800 |
+
|
801 |
+
if ( ! this.enablePan && ! this.enableZoom ) {
|
802 |
+
|
803 |
+
this.activateGizmos( true );
|
804 |
+
|
805 |
+
}
|
806 |
+
|
807 |
+
}
|
808 |
+
|
809 |
+
}
|
810 |
+
|
811 |
+
onRotateMove() {
|
812 |
+
|
813 |
+
if ( this.enabled && this.enableRotate ) {
|
814 |
+
|
815 |
+
this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
|
816 |
+
let rotationPoint;
|
817 |
+
|
818 |
+
if ( this._state != STATE.ZROTATE ) {
|
819 |
+
|
820 |
+
this.updateTbState( STATE.ZROTATE, true );
|
821 |
+
this._startFingerRotation = this._currentFingerRotation;
|
822 |
+
|
823 |
+
}
|
824 |
+
|
825 |
+
//this._currentFingerRotation = event.rotation;
|
826 |
+
this._currentFingerRotation = this.getAngle( this._touchCurrent[ 1 ], this._touchCurrent[ 0 ] ) + this.getAngle( this._touchStart[ 1 ], this._touchStart[ 0 ] );
|
827 |
+
|
828 |
+
if ( ! this.enablePan ) {
|
829 |
+
|
830 |
+
rotationPoint = new Vector3().setFromMatrixPosition( this._gizmoMatrixState );
|
831 |
+
|
832 |
+
} else {
|
833 |
+
|
834 |
+
this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
|
835 |
+
rotationPoint = this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement ).applyQuaternion( this.object.quaternion ).multiplyScalar( 1 / this.object.zoom ).add( this._v3_2 );
|
836 |
+
|
837 |
+
}
|
838 |
+
|
839 |
+
const amount = MathUtils.DEG2RAD * ( this._startFingerRotation - this._currentFingerRotation );
|
840 |
+
|
841 |
+
this.applyTransformMatrix( this.zRotate( rotationPoint, amount ) );
|
842 |
+
this.dispatchEvent( _changeEvent );
|
843 |
+
|
844 |
+
}
|
845 |
+
|
846 |
+
}
|
847 |
+
|
848 |
+
onRotateEnd() {
|
849 |
+
|
850 |
+
this.updateTbState( STATE.IDLE, false );
|
851 |
+
this.activateGizmos( false );
|
852 |
+
this.dispatchEvent( _endEvent );
|
853 |
+
|
854 |
+
}
|
855 |
+
|
856 |
+
onPinchStart() {
|
857 |
+
|
858 |
+
if ( this.enabled && this.enableZoom ) {
|
859 |
+
|
860 |
+
this.dispatchEvent( _startEvent );
|
861 |
+
this.updateTbState( STATE.SCALE, true );
|
862 |
+
|
863 |
+
this._startFingerDistance = this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] );
|
864 |
+
this._currentFingerDistance = this._startFingerDistance;
|
865 |
+
|
866 |
+
this.activateGizmos( false );
|
867 |
+
|
868 |
+
}
|
869 |
+
|
870 |
+
}
|
871 |
+
|
872 |
+
onPinchMove() {
|
873 |
+
|
874 |
+
if ( this.enabled && this.enableZoom ) {
|
875 |
+
|
876 |
+
this.setCenter( ( this._touchCurrent[ 0 ].clientX + this._touchCurrent[ 1 ].clientX ) / 2, ( this._touchCurrent[ 0 ].clientY + this._touchCurrent[ 1 ].clientY ) / 2 );
|
877 |
+
const minDistance = 12; //minimum distance between fingers (in css pixels)
|
878 |
+
|
879 |
+
if ( this._state != STATE.SCALE ) {
|
880 |
+
|
881 |
+
this._startFingerDistance = this._currentFingerDistance;
|
882 |
+
this.updateTbState( STATE.SCALE, true );
|
883 |
+
|
884 |
+
}
|
885 |
+
|
886 |
+
this._currentFingerDistance = Math.max( this.calculatePointersDistance( this._touchCurrent[ 0 ], this._touchCurrent[ 1 ] ), minDistance * this._devPxRatio );
|
887 |
+
const amount = this._currentFingerDistance / this._startFingerDistance;
|
888 |
+
|
889 |
+
let scalePoint;
|
890 |
+
|
891 |
+
if ( ! this.enablePan ) {
|
892 |
+
|
893 |
+
scalePoint = this._gizmos.position;
|
894 |
+
|
895 |
+
} else {
|
896 |
+
|
897 |
+
if ( this.object.isOrthographicCamera ) {
|
898 |
+
|
899 |
+
scalePoint = this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement )
|
900 |
+
.applyQuaternion( this.object.quaternion )
|
901 |
+
.multiplyScalar( 1 / this.object.zoom )
|
902 |
+
.add( this._gizmos.position );
|
903 |
+
|
904 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
905 |
+
|
906 |
+
scalePoint = this.unprojectOnTbPlane( this.object, _center.x, _center.y, this.domElement )
|
907 |
+
.applyQuaternion( this.object.quaternion )
|
908 |
+
.add( this._gizmos.position );
|
909 |
+
|
910 |
+
}
|
911 |
+
|
912 |
+
}
|
913 |
+
|
914 |
+
this.applyTransformMatrix( this.scale( amount, scalePoint ) );
|
915 |
+
this.dispatchEvent( _changeEvent );
|
916 |
+
|
917 |
+
}
|
918 |
+
|
919 |
+
}
|
920 |
+
|
921 |
+
onPinchEnd() {
|
922 |
+
|
923 |
+
this.updateTbState( STATE.IDLE, false );
|
924 |
+
this.dispatchEvent( _endEvent );
|
925 |
+
|
926 |
+
}
|
927 |
+
|
928 |
+
onTriplePanStart() {
|
929 |
+
|
930 |
+
if ( this.enabled && this.enableZoom ) {
|
931 |
+
|
932 |
+
this.dispatchEvent( _startEvent );
|
933 |
+
|
934 |
+
this.updateTbState( STATE.SCALE, true );
|
935 |
+
|
936 |
+
//const center = event.center;
|
937 |
+
let clientX = 0;
|
938 |
+
let clientY = 0;
|
939 |
+
const nFingers = this._touchCurrent.length;
|
940 |
+
|
941 |
+
for ( let i = 0; i < nFingers; i ++ ) {
|
942 |
+
|
943 |
+
clientX += this._touchCurrent[ i ].clientX;
|
944 |
+
clientY += this._touchCurrent[ i ].clientY;
|
945 |
+
|
946 |
+
}
|
947 |
+
|
948 |
+
this.setCenter( clientX / nFingers, clientY / nFingers );
|
949 |
+
|
950 |
+
this._startCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
951 |
+
this._currentCursorPosition.copy( this._startCursorPosition );
|
952 |
+
|
953 |
+
}
|
954 |
+
|
955 |
+
}
|
956 |
+
|
957 |
+
onTriplePanMove() {
|
958 |
+
|
959 |
+
if ( this.enabled && this.enableZoom ) {
|
960 |
+
|
961 |
+
// fov / 2
|
962 |
+
// |\
|
963 |
+
// | \
|
964 |
+
// | \
|
965 |
+
// x | \
|
966 |
+
// | \
|
967 |
+
// | \
|
968 |
+
// | _ _ _\
|
969 |
+
// y
|
970 |
+
|
971 |
+
//const center = event.center;
|
972 |
+
let clientX = 0;
|
973 |
+
let clientY = 0;
|
974 |
+
const nFingers = this._touchCurrent.length;
|
975 |
+
|
976 |
+
for ( let i = 0; i < nFingers; i ++ ) {
|
977 |
+
|
978 |
+
clientX += this._touchCurrent[ i ].clientX;
|
979 |
+
clientY += this._touchCurrent[ i ].clientY;
|
980 |
+
|
981 |
+
}
|
982 |
+
|
983 |
+
this.setCenter( clientX / nFingers, clientY / nFingers );
|
984 |
+
|
985 |
+
const screenNotches = 8; //how many wheel notches corresponds to a full screen pan
|
986 |
+
this._currentCursorPosition.setY( this.getCursorNDC( _center.x, _center.y, this.domElement ).y * 0.5 );
|
987 |
+
|
988 |
+
const movement = this._currentCursorPosition.y - this._startCursorPosition.y;
|
989 |
+
|
990 |
+
let size = 1;
|
991 |
+
|
992 |
+
if ( movement < 0 ) {
|
993 |
+
|
994 |
+
size = 1 / ( Math.pow( this.scaleFactor, - movement * screenNotches ) );
|
995 |
+
|
996 |
+
} else if ( movement > 0 ) {
|
997 |
+
|
998 |
+
size = Math.pow( this.scaleFactor, movement * screenNotches );
|
999 |
+
|
1000 |
+
}
|
1001 |
+
|
1002 |
+
this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
|
1003 |
+
const x = this._v3_1.distanceTo( this._gizmos.position );
|
1004 |
+
let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
|
1005 |
+
|
1006 |
+
//check min and max distance
|
1007 |
+
xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
|
1008 |
+
|
1009 |
+
const y = x * Math.tan( MathUtils.DEG2RAD * this._fovState * 0.5 );
|
1010 |
+
|
1011 |
+
//calculate new fov
|
1012 |
+
let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
|
1013 |
+
|
1014 |
+
//check min and max fov
|
1015 |
+
newFov = MathUtils.clamp( newFov, this.minFov, this.maxFov );
|
1016 |
+
|
1017 |
+
const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
|
1018 |
+
size = x / newDistance;
|
1019 |
+
this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
|
1020 |
+
|
1021 |
+
this.setFov( newFov );
|
1022 |
+
this.applyTransformMatrix( this.scale( size, this._v3_2, false ) );
|
1023 |
+
|
1024 |
+
//adjusting distance
|
1025 |
+
_offset.copy( this._gizmos.position ).sub( this.object.position ).normalize().multiplyScalar( newDistance / x );
|
1026 |
+
this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
|
1027 |
+
|
1028 |
+
this.dispatchEvent( _changeEvent );
|
1029 |
+
|
1030 |
+
}
|
1031 |
+
|
1032 |
+
}
|
1033 |
+
|
1034 |
+
onTriplePanEnd() {
|
1035 |
+
|
1036 |
+
this.updateTbState( STATE.IDLE, false );
|
1037 |
+
this.dispatchEvent( _endEvent );
|
1038 |
+
//this.dispatchEvent( _changeEvent );
|
1039 |
+
|
1040 |
+
}
|
1041 |
+
|
1042 |
+
/**
|
1043 |
+
* Set _center's x/y coordinates
|
1044 |
+
* @param {Number} clientX
|
1045 |
+
* @param {Number} clientY
|
1046 |
+
*/
|
1047 |
+
setCenter( clientX, clientY ) {
|
1048 |
+
|
1049 |
+
_center.x = clientX;
|
1050 |
+
_center.y = clientY;
|
1051 |
+
|
1052 |
+
}
|
1053 |
+
|
1054 |
+
/**
|
1055 |
+
* Set default mouse actions
|
1056 |
+
*/
|
1057 |
+
initializeMouseActions() {
|
1058 |
+
|
1059 |
+
this.setMouseAction( 'PAN', 0, 'CTRL' );
|
1060 |
+
this.setMouseAction( 'PAN', 2 );
|
1061 |
+
|
1062 |
+
this.setMouseAction( 'ROTATE', 0 );
|
1063 |
+
|
1064 |
+
this.setMouseAction( 'ZOOM', 'WHEEL' );
|
1065 |
+
this.setMouseAction( 'ZOOM', 1 );
|
1066 |
+
|
1067 |
+
this.setMouseAction( 'FOV', 'WHEEL', 'SHIFT' );
|
1068 |
+
this.setMouseAction( 'FOV', 1, 'SHIFT' );
|
1069 |
+
|
1070 |
+
|
1071 |
+
}
|
1072 |
+
|
1073 |
+
/**
|
1074 |
+
* Compare two mouse actions
|
1075 |
+
* @param {Object} action1
|
1076 |
+
* @param {Object} action2
|
1077 |
+
* @returns {Boolean} True if action1 and action 2 are the same mouse action, false otherwise
|
1078 |
+
*/
|
1079 |
+
compareMouseAction( action1, action2 ) {
|
1080 |
+
|
1081 |
+
if ( action1.operation == action2.operation ) {
|
1082 |
+
|
1083 |
+
if ( action1.mouse == action2.mouse && action1.key == action2.key ) {
|
1084 |
+
|
1085 |
+
return true;
|
1086 |
+
|
1087 |
+
} else {
|
1088 |
+
|
1089 |
+
return false;
|
1090 |
+
|
1091 |
+
}
|
1092 |
+
|
1093 |
+
} else {
|
1094 |
+
|
1095 |
+
return false;
|
1096 |
+
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
}
|
1100 |
+
|
1101 |
+
/**
|
1102 |
+
* Set a new mouse action by specifying the operation to be performed and a mouse/key combination. In case of conflict, replaces the existing one
|
1103 |
+
* @param {String} operation The operation to be performed ('PAN', 'ROTATE', 'ZOOM', 'FOV)
|
1104 |
+
* @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
|
1105 |
+
* @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
|
1106 |
+
* @returns {Boolean} True if the mouse action has been successfully added, false otherwise
|
1107 |
+
*/
|
1108 |
+
setMouseAction( operation, mouse, key = null ) {
|
1109 |
+
|
1110 |
+
const operationInput = [ 'PAN', 'ROTATE', 'ZOOM', 'FOV' ];
|
1111 |
+
const mouseInput = [ 0, 1, 2, 'WHEEL' ];
|
1112 |
+
const keyInput = [ 'CTRL', 'SHIFT', null ];
|
1113 |
+
let state;
|
1114 |
+
|
1115 |
+
if ( ! operationInput.includes( operation ) || ! mouseInput.includes( mouse ) || ! keyInput.includes( key ) ) {
|
1116 |
+
|
1117 |
+
//invalid parameters
|
1118 |
+
return false;
|
1119 |
+
|
1120 |
+
}
|
1121 |
+
|
1122 |
+
if ( mouse == 'WHEEL' ) {
|
1123 |
+
|
1124 |
+
if ( operation != 'ZOOM' && operation != 'FOV' ) {
|
1125 |
+
|
1126 |
+
//cannot associate 2D operation to 1D input
|
1127 |
+
return false;
|
1128 |
+
|
1129 |
+
}
|
1130 |
+
|
1131 |
+
}
|
1132 |
+
|
1133 |
+
switch ( operation ) {
|
1134 |
+
|
1135 |
+
case 'PAN':
|
1136 |
+
|
1137 |
+
state = STATE.PAN;
|
1138 |
+
break;
|
1139 |
+
|
1140 |
+
case 'ROTATE':
|
1141 |
+
|
1142 |
+
state = STATE.ROTATE;
|
1143 |
+
break;
|
1144 |
+
|
1145 |
+
case 'ZOOM':
|
1146 |
+
|
1147 |
+
state = STATE.SCALE;
|
1148 |
+
break;
|
1149 |
+
|
1150 |
+
case 'FOV':
|
1151 |
+
|
1152 |
+
state = STATE.FOV;
|
1153 |
+
break;
|
1154 |
+
|
1155 |
+
}
|
1156 |
+
|
1157 |
+
const action = {
|
1158 |
+
|
1159 |
+
operation: operation,
|
1160 |
+
mouse: mouse,
|
1161 |
+
key: key,
|
1162 |
+
state: state
|
1163 |
+
|
1164 |
+
};
|
1165 |
+
|
1166 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
1167 |
+
|
1168 |
+
if ( this.mouseActions[ i ].mouse == action.mouse && this.mouseActions[ i ].key == action.key ) {
|
1169 |
+
|
1170 |
+
this.mouseActions.splice( i, 1, action );
|
1171 |
+
return true;
|
1172 |
+
|
1173 |
+
}
|
1174 |
+
|
1175 |
+
}
|
1176 |
+
|
1177 |
+
this.mouseActions.push( action );
|
1178 |
+
return true;
|
1179 |
+
|
1180 |
+
}
|
1181 |
+
|
1182 |
+
/**
|
1183 |
+
* Remove a mouse action by specifying its mouse/key combination
|
1184 |
+
* @param {*} mouse A mouse button (0, 1, 2) or 'WHEEL' for wheel notches
|
1185 |
+
* @param {*} key The keyboard modifier ('CTRL', 'SHIFT') or null if key is not needed
|
1186 |
+
* @returns {Boolean} True if the operation has been successfully removed, false otherwise
|
1187 |
+
*/
|
1188 |
+
unsetMouseAction( mouse, key = null ) {
|
1189 |
+
|
1190 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
1191 |
+
|
1192 |
+
if ( this.mouseActions[ i ].mouse == mouse && this.mouseActions[ i ].key == key ) {
|
1193 |
+
|
1194 |
+
this.mouseActions.splice( i, 1 );
|
1195 |
+
return true;
|
1196 |
+
|
1197 |
+
}
|
1198 |
+
|
1199 |
+
}
|
1200 |
+
|
1201 |
+
return false;
|
1202 |
+
|
1203 |
+
}
|
1204 |
+
|
1205 |
+
/**
|
1206 |
+
* Return the operation associated to a mouse/keyboard combination
|
1207 |
+
* @param {0|1|2|'WHEEL'} mouse Mouse button index (0, 1, 2) or 'WHEEL' for wheel notches
|
1208 |
+
* @param {'CTRL'|'SHIFT'|null} key Keyboard modifier
|
1209 |
+
* @returns {string|null} The operation if it has been found, null otherwise
|
1210 |
+
*/
|
1211 |
+
getOpFromAction( mouse, key ) {
|
1212 |
+
|
1213 |
+
let action;
|
1214 |
+
|
1215 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
1216 |
+
|
1217 |
+
action = this.mouseActions[ i ];
|
1218 |
+
if ( action.mouse == mouse && action.key == key ) {
|
1219 |
+
|
1220 |
+
return action.operation;
|
1221 |
+
|
1222 |
+
}
|
1223 |
+
|
1224 |
+
}
|
1225 |
+
|
1226 |
+
if ( key != null ) {
|
1227 |
+
|
1228 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
1229 |
+
|
1230 |
+
action = this.mouseActions[ i ];
|
1231 |
+
if ( action.mouse == mouse && action.key == null ) {
|
1232 |
+
|
1233 |
+
return action.operation;
|
1234 |
+
|
1235 |
+
}
|
1236 |
+
|
1237 |
+
}
|
1238 |
+
|
1239 |
+
}
|
1240 |
+
|
1241 |
+
return null;
|
1242 |
+
|
1243 |
+
}
|
1244 |
+
|
1245 |
+
/**
|
1246 |
+
* Get the operation associated to mouse and key combination and returns the corresponding FSA state
|
1247 |
+
* @param {0|1|2} mouse Mouse button index (0, 1, 2)
|
1248 |
+
* @param {'CTRL'|'SHIFT'|null} key Keyboard modifier
|
1249 |
+
* @returns {STATE|null} The FSA state obtained from the operation associated to mouse/keyboard combination
|
1250 |
+
*/
|
1251 |
+
getOpStateFromAction( mouse, key ) {
|
1252 |
+
|
1253 |
+
let action;
|
1254 |
+
|
1255 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
1256 |
+
|
1257 |
+
action = this.mouseActions[ i ];
|
1258 |
+
if ( action.mouse == mouse && action.key == key ) {
|
1259 |
+
|
1260 |
+
return action.state;
|
1261 |
+
|
1262 |
+
}
|
1263 |
+
|
1264 |
+
}
|
1265 |
+
|
1266 |
+
if ( key != null ) {
|
1267 |
+
|
1268 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
1269 |
+
|
1270 |
+
action = this.mouseActions[ i ];
|
1271 |
+
if ( action.mouse == mouse && action.key == null ) {
|
1272 |
+
|
1273 |
+
return action.state;
|
1274 |
+
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
}
|
1278 |
+
|
1279 |
+
}
|
1280 |
+
|
1281 |
+
return null;
|
1282 |
+
|
1283 |
+
}
|
1284 |
+
|
1285 |
+
/**
|
1286 |
+
* Calculate the angle between two pointers
|
1287 |
+
* @param {PointerEvent} p1
|
1288 |
+
* @param {PointerEvent} p2
|
1289 |
+
* @returns {Number} The angle between two pointers in degrees
|
1290 |
+
*/
|
1291 |
+
getAngle( p1, p2 ) {
|
1292 |
+
|
1293 |
+
return Math.atan2( p2.clientY - p1.clientY, p2.clientX - p1.clientX ) * 180 / Math.PI;
|
1294 |
+
|
1295 |
+
}
|
1296 |
+
|
1297 |
+
/**
|
1298 |
+
* Update a PointerEvent inside current pointerevents array
|
1299 |
+
* @param {PointerEvent} event
|
1300 |
+
*/
|
1301 |
+
updateTouchEvent( event ) {
|
1302 |
+
|
1303 |
+
for ( let i = 0; i < this._touchCurrent.length; i ++ ) {
|
1304 |
+
|
1305 |
+
if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
|
1306 |
+
|
1307 |
+
this._touchCurrent.splice( i, 1, event );
|
1308 |
+
break;
|
1309 |
+
|
1310 |
+
}
|
1311 |
+
|
1312 |
+
}
|
1313 |
+
|
1314 |
+
}
|
1315 |
+
|
1316 |
+
/**
|
1317 |
+
* Apply a transformation matrix, to the camera and gizmos
|
1318 |
+
* @param {Object} transformation Object containing matrices to apply to camera and gizmos
|
1319 |
+
*/
|
1320 |
+
applyTransformMatrix( transformation ) {
|
1321 |
+
|
1322 |
+
if ( transformation.camera != null ) {
|
1323 |
+
|
1324 |
+
this._m4_1.copy( this._cameraMatrixState ).premultiply( transformation.camera );
|
1325 |
+
this._m4_1.decompose( this.object.position, this.object.quaternion, this.object.scale );
|
1326 |
+
this.object.updateMatrix();
|
1327 |
+
|
1328 |
+
//update camera up vector
|
1329 |
+
if ( this._state == STATE.ROTATE || this._state == STATE.ZROTATE || this._state == STATE.ANIMATION_ROTATE ) {
|
1330 |
+
|
1331 |
+
this.object.up.copy( this._upState ).applyQuaternion( this.object.quaternion );
|
1332 |
+
|
1333 |
+
}
|
1334 |
+
|
1335 |
+
}
|
1336 |
+
|
1337 |
+
if ( transformation.gizmos != null ) {
|
1338 |
+
|
1339 |
+
this._m4_1.copy( this._gizmoMatrixState ).premultiply( transformation.gizmos );
|
1340 |
+
this._m4_1.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
1341 |
+
this._gizmos.updateMatrix();
|
1342 |
+
|
1343 |
+
}
|
1344 |
+
|
1345 |
+
if ( this._state == STATE.SCALE || this._state == STATE.FOCUS || this._state == STATE.ANIMATION_FOCUS ) {
|
1346 |
+
|
1347 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
1348 |
+
|
1349 |
+
if ( this.adjustNearFar ) {
|
1350 |
+
|
1351 |
+
const cameraDistance = this.object.position.distanceTo( this._gizmos.position );
|
1352 |
+
|
1353 |
+
const bb = new Box3();
|
1354 |
+
bb.setFromObject( this._gizmos );
|
1355 |
+
const sphere = new Sphere();
|
1356 |
+
bb.getBoundingSphere( sphere );
|
1357 |
+
|
1358 |
+
const adjustedNearPosition = Math.max( this._nearPos0, sphere.radius + sphere.center.length() );
|
1359 |
+
const regularNearPosition = cameraDistance - this._initialNear;
|
1360 |
+
|
1361 |
+
const minNearPos = Math.min( adjustedNearPosition, regularNearPosition );
|
1362 |
+
this.object.near = cameraDistance - minNearPos;
|
1363 |
+
|
1364 |
+
|
1365 |
+
const adjustedFarPosition = Math.min( this._farPos0, - sphere.radius + sphere.center.length() );
|
1366 |
+
const regularFarPosition = cameraDistance - this._initialFar;
|
1367 |
+
|
1368 |
+
const minFarPos = Math.min( adjustedFarPosition, regularFarPosition );
|
1369 |
+
this.object.far = cameraDistance - minFarPos;
|
1370 |
+
|
1371 |
+
this.object.updateProjectionMatrix();
|
1372 |
+
|
1373 |
+
} else {
|
1374 |
+
|
1375 |
+
let update = false;
|
1376 |
+
|
1377 |
+
if ( this.object.near != this._initialNear ) {
|
1378 |
+
|
1379 |
+
this.object.near = this._initialNear;
|
1380 |
+
update = true;
|
1381 |
+
|
1382 |
+
}
|
1383 |
+
|
1384 |
+
if ( this.object.far != this._initialFar ) {
|
1385 |
+
|
1386 |
+
this.object.far = this._initialFar;
|
1387 |
+
update = true;
|
1388 |
+
|
1389 |
+
}
|
1390 |
+
|
1391 |
+
if ( update ) {
|
1392 |
+
|
1393 |
+
this.object.updateProjectionMatrix();
|
1394 |
+
|
1395 |
+
}
|
1396 |
+
|
1397 |
+
}
|
1398 |
+
|
1399 |
+
}
|
1400 |
+
|
1401 |
+
}
|
1402 |
+
|
1403 |
+
/**
|
1404 |
+
* Calculate the angular speed
|
1405 |
+
*
|
1406 |
+
* @param {Number} p0 Position at t0
|
1407 |
+
* @param {Number} p1 Position at t1
|
1408 |
+
* @param {Number} t0 Initial time in milliseconds
|
1409 |
+
* @param {Number} t1 Ending time in milliseconds
|
1410 |
+
* @returns {Number}
|
1411 |
+
*/
|
1412 |
+
calculateAngularSpeed( p0, p1, t0, t1 ) {
|
1413 |
+
|
1414 |
+
const s = p1 - p0;
|
1415 |
+
const t = ( t1 - t0 ) / 1000;
|
1416 |
+
if ( t == 0 ) {
|
1417 |
+
|
1418 |
+
return 0;
|
1419 |
+
|
1420 |
+
}
|
1421 |
+
|
1422 |
+
return s / t;
|
1423 |
+
|
1424 |
+
}
|
1425 |
+
|
1426 |
+
/**
|
1427 |
+
* Calculate the distance between two pointers
|
1428 |
+
* @param {PointerEvent} p0 The first pointer
|
1429 |
+
* @param {PointerEvent} p1 The second pointer
|
1430 |
+
* @returns {number} The distance between the two pointers
|
1431 |
+
*/
|
1432 |
+
calculatePointersDistance( p0, p1 ) {
|
1433 |
+
|
1434 |
+
return Math.sqrt( Math.pow( p1.clientX - p0.clientX, 2 ) + Math.pow( p1.clientY - p0.clientY, 2 ) );
|
1435 |
+
|
1436 |
+
}
|
1437 |
+
|
1438 |
+
/**
|
1439 |
+
* Calculate the rotation axis as the vector perpendicular between two vectors
|
1440 |
+
* @param {Vector3} vec1 The first vector
|
1441 |
+
* @param {Vector3} vec2 The second vector
|
1442 |
+
* @returns {Vector3} The normalized rotation axis
|
1443 |
+
*/
|
1444 |
+
calculateRotationAxis( vec1, vec2 ) {
|
1445 |
+
|
1446 |
+
this._rotationMatrix.extractRotation( this._cameraMatrixState );
|
1447 |
+
this._quat.setFromRotationMatrix( this._rotationMatrix );
|
1448 |
+
|
1449 |
+
this._rotationAxis.crossVectors( vec1, vec2 ).applyQuaternion( this._quat );
|
1450 |
+
return this._rotationAxis.normalize().clone();
|
1451 |
+
|
1452 |
+
}
|
1453 |
+
|
1454 |
+
/**
|
1455 |
+
* Calculate the trackball radius so that gizmo's diameter will be 2/3 of the minimum side of the camera frustum
|
1456 |
+
* @param {Camera} camera
|
1457 |
+
* @returns {Number} The trackball radius
|
1458 |
+
*/
|
1459 |
+
calculateTbRadius( camera ) {
|
1460 |
+
|
1461 |
+
const distance = camera.position.distanceTo( this._gizmos.position );
|
1462 |
+
|
1463 |
+
if ( camera.type == 'PerspectiveCamera' ) {
|
1464 |
+
|
1465 |
+
const halfFovV = MathUtils.DEG2RAD * camera.fov * 0.5; //vertical fov/2 in radians
|
1466 |
+
const halfFovH = Math.atan( ( camera.aspect ) * Math.tan( halfFovV ) ); //horizontal fov/2 in radians
|
1467 |
+
return Math.tan( Math.min( halfFovV, halfFovH ) ) * distance * this.radiusFactor;
|
1468 |
+
|
1469 |
+
} else if ( camera.type == 'OrthographicCamera' ) {
|
1470 |
+
|
1471 |
+
return Math.min( camera.top, camera.right ) * this.radiusFactor;
|
1472 |
+
|
1473 |
+
}
|
1474 |
+
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
/**
|
1478 |
+
* Focus operation consist of positioning the point of interest in front of the camera and a slightly zoom in
|
1479 |
+
* @param {Vector3} point The point of interest
|
1480 |
+
* @param {Number} size Scale factor
|
1481 |
+
* @param {Number} amount Amount of operation to be completed (used for focus animations, default is complete full operation)
|
1482 |
+
*/
|
1483 |
+
focus( point, size, amount = 1 ) {
|
1484 |
+
|
1485 |
+
//move center of camera (along with gizmos) towards point of interest
|
1486 |
+
_offset.copy( point ).sub( this._gizmos.position ).multiplyScalar( amount );
|
1487 |
+
this._translationMatrix.makeTranslation( _offset.x, _offset.y, _offset.z );
|
1488 |
+
|
1489 |
+
_gizmoMatrixStateTemp.copy( this._gizmoMatrixState );
|
1490 |
+
this._gizmoMatrixState.premultiply( this._translationMatrix );
|
1491 |
+
this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
1492 |
+
|
1493 |
+
_cameraMatrixStateTemp.copy( this._cameraMatrixState );
|
1494 |
+
this._cameraMatrixState.premultiply( this._translationMatrix );
|
1495 |
+
this._cameraMatrixState.decompose( this.object.position, this.object.quaternion, this.object.scale );
|
1496 |
+
|
1497 |
+
//apply zoom
|
1498 |
+
if ( this.enableZoom ) {
|
1499 |
+
|
1500 |
+
this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
|
1501 |
+
|
1502 |
+
}
|
1503 |
+
|
1504 |
+
this._gizmoMatrixState.copy( _gizmoMatrixStateTemp );
|
1505 |
+
this._cameraMatrixState.copy( _cameraMatrixStateTemp );
|
1506 |
+
|
1507 |
+
}
|
1508 |
+
|
1509 |
+
/**
|
1510 |
+
* Draw a grid and add it to the scene
|
1511 |
+
*/
|
1512 |
+
drawGrid() {
|
1513 |
+
|
1514 |
+
if ( this.scene != null ) {
|
1515 |
+
|
1516 |
+
const color = 0x888888;
|
1517 |
+
const multiplier = 3;
|
1518 |
+
let size, divisions, maxLength, tick;
|
1519 |
+
|
1520 |
+
if ( this.object.isOrthographicCamera ) {
|
1521 |
+
|
1522 |
+
const width = this.object.right - this.object.left;
|
1523 |
+
const height = this.object.bottom - this.object.top;
|
1524 |
+
|
1525 |
+
maxLength = Math.max( width, height );
|
1526 |
+
tick = maxLength / 20;
|
1527 |
+
|
1528 |
+
size = maxLength / this.object.zoom * multiplier;
|
1529 |
+
divisions = size / tick * this.object.zoom;
|
1530 |
+
|
1531 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
1532 |
+
|
1533 |
+
const distance = this.object.position.distanceTo( this._gizmos.position );
|
1534 |
+
const halfFovV = MathUtils.DEG2RAD * this.object.fov * 0.5;
|
1535 |
+
const halfFovH = Math.atan( ( this.object.aspect ) * Math.tan( halfFovV ) );
|
1536 |
+
|
1537 |
+
maxLength = Math.tan( Math.max( halfFovV, halfFovH ) ) * distance * 2;
|
1538 |
+
tick = maxLength / 20;
|
1539 |
+
|
1540 |
+
size = maxLength * multiplier;
|
1541 |
+
divisions = size / tick;
|
1542 |
+
|
1543 |
+
}
|
1544 |
+
|
1545 |
+
if ( this._grid == null ) {
|
1546 |
+
|
1547 |
+
this._grid = new GridHelper( size, divisions, color, color );
|
1548 |
+
this._grid.position.copy( this._gizmos.position );
|
1549 |
+
this._gridPosition.copy( this._grid.position );
|
1550 |
+
this._grid.quaternion.copy( this.object.quaternion );
|
1551 |
+
this._grid.rotateX( Math.PI * 0.5 );
|
1552 |
+
|
1553 |
+
this.scene.add( this._grid );
|
1554 |
+
|
1555 |
+
}
|
1556 |
+
|
1557 |
+
}
|
1558 |
+
|
1559 |
+
}
|
1560 |
+
|
1561 |
+
/**
|
1562 |
+
* Remove all listeners, stop animations and clean scene
|
1563 |
+
*/
|
1564 |
+
dispose() {
|
1565 |
+
|
1566 |
+
if ( this._animationId != - 1 ) {
|
1567 |
+
|
1568 |
+
window.cancelAnimationFrame( this._animationId );
|
1569 |
+
|
1570 |
+
}
|
1571 |
+
|
1572 |
+
this.disconnect();
|
1573 |
+
|
1574 |
+
if ( this.scene !== null ) this.scene.remove( this._gizmos );
|
1575 |
+
this.disposeGrid();
|
1576 |
+
|
1577 |
+
}
|
1578 |
+
|
1579 |
+
/**
|
1580 |
+
* remove the grid from the scene
|
1581 |
+
*/
|
1582 |
+
disposeGrid() {
|
1583 |
+
|
1584 |
+
if ( this._grid != null && this.scene != null ) {
|
1585 |
+
|
1586 |
+
this.scene.remove( this._grid );
|
1587 |
+
this._grid = null;
|
1588 |
+
|
1589 |
+
}
|
1590 |
+
|
1591 |
+
}
|
1592 |
+
|
1593 |
+
/**
|
1594 |
+
* Compute the easing out cubic function for ease out effect in animation
|
1595 |
+
* @param {Number} t The absolute progress of the animation in the bound of 0 (beginning of the) and 1 (ending of animation)
|
1596 |
+
* @returns {Number} Result of easing out cubic at time t
|
1597 |
+
*/
|
1598 |
+
easeOutCubic( t ) {
|
1599 |
+
|
1600 |
+
return 1 - Math.pow( 1 - t, 3 );
|
1601 |
+
|
1602 |
+
}
|
1603 |
+
|
1604 |
+
/**
|
1605 |
+
* Make rotation gizmos more or less visible
|
1606 |
+
* @param {Boolean} isActive If true, make gizmos more visible
|
1607 |
+
*/
|
1608 |
+
activateGizmos( isActive ) {
|
1609 |
+
|
1610 |
+
const gizmoX = this._gizmos.children[ 0 ];
|
1611 |
+
const gizmoY = this._gizmos.children[ 1 ];
|
1612 |
+
const gizmoZ = this._gizmos.children[ 2 ];
|
1613 |
+
|
1614 |
+
if ( isActive ) {
|
1615 |
+
|
1616 |
+
gizmoX.material.setValues( { opacity: 1 } );
|
1617 |
+
gizmoY.material.setValues( { opacity: 1 } );
|
1618 |
+
gizmoZ.material.setValues( { opacity: 1 } );
|
1619 |
+
|
1620 |
+
} else {
|
1621 |
+
|
1622 |
+
gizmoX.material.setValues( { opacity: 0.6 } );
|
1623 |
+
gizmoY.material.setValues( { opacity: 0.6 } );
|
1624 |
+
gizmoZ.material.setValues( { opacity: 0.6 } );
|
1625 |
+
|
1626 |
+
}
|
1627 |
+
|
1628 |
+
}
|
1629 |
+
|
1630 |
+
/**
|
1631 |
+
* Calculate the cursor position in NDC
|
1632 |
+
*
|
1633 |
+
* @param {number} cursorX Cursor horizontal coordinate within the canvas
|
1634 |
+
* @param {number} cursorY Cursor vertical coordinate within the canvas
|
1635 |
+
* @param {HTMLElement} canvas The canvas where the renderer draws its output
|
1636 |
+
* @returns {Vector2} Cursor normalized position inside the canvas
|
1637 |
+
*/
|
1638 |
+
getCursorNDC( cursorX, cursorY, canvas ) {
|
1639 |
+
|
1640 |
+
const canvasRect = canvas.getBoundingClientRect();
|
1641 |
+
this._v2_1.setX( ( ( cursorX - canvasRect.left ) / canvasRect.width ) * 2 - 1 );
|
1642 |
+
this._v2_1.setY( ( ( canvasRect.bottom - cursorY ) / canvasRect.height ) * 2 - 1 );
|
1643 |
+
return this._v2_1.clone();
|
1644 |
+
|
1645 |
+
}
|
1646 |
+
|
1647 |
+
/**
|
1648 |
+
* Calculate the cursor position inside the canvas x/y coordinates with the origin being in the center of the canvas
|
1649 |
+
*
|
1650 |
+
* @param {Number} cursorX Cursor horizontal coordinate within the canvas
|
1651 |
+
* @param {Number} cursorY Cursor vertical coordinate within the canvas
|
1652 |
+
* @param {HTMLElement} canvas The canvas where the renderer draws its output
|
1653 |
+
* @returns {Vector2} Cursor position inside the canvas
|
1654 |
+
*/
|
1655 |
+
getCursorPosition( cursorX, cursorY, canvas ) {
|
1656 |
+
|
1657 |
+
this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
|
1658 |
+
this._v2_1.x *= ( this.object.right - this.object.left ) * 0.5;
|
1659 |
+
this._v2_1.y *= ( this.object.top - this.object.bottom ) * 0.5;
|
1660 |
+
return this._v2_1.clone();
|
1661 |
+
|
1662 |
+
}
|
1663 |
+
|
1664 |
+
/**
|
1665 |
+
* Set the camera to be controlled
|
1666 |
+
* @param {Camera} camera The virtual camera to be controlled
|
1667 |
+
*/
|
1668 |
+
setCamera( camera ) {
|
1669 |
+
|
1670 |
+
camera.lookAt( this.target );
|
1671 |
+
camera.updateMatrix();
|
1672 |
+
|
1673 |
+
//setting state
|
1674 |
+
if ( camera.type == 'PerspectiveCamera' ) {
|
1675 |
+
|
1676 |
+
this._fov0 = camera.fov;
|
1677 |
+
this._fovState = camera.fov;
|
1678 |
+
|
1679 |
+
}
|
1680 |
+
|
1681 |
+
this._cameraMatrixState0.copy( camera.matrix );
|
1682 |
+
this._cameraMatrixState.copy( this._cameraMatrixState0 );
|
1683 |
+
this._cameraProjectionState.copy( camera.projectionMatrix );
|
1684 |
+
this._zoom0 = camera.zoom;
|
1685 |
+
this._zoomState = this._zoom0;
|
1686 |
+
|
1687 |
+
this._initialNear = camera.near;
|
1688 |
+
this._nearPos0 = camera.position.distanceTo( this.target ) - camera.near;
|
1689 |
+
this._nearPos = this._initialNear;
|
1690 |
+
|
1691 |
+
this._initialFar = camera.far;
|
1692 |
+
this._farPos0 = camera.position.distanceTo( this.target ) - camera.far;
|
1693 |
+
this._farPos = this._initialFar;
|
1694 |
+
|
1695 |
+
this._up0.copy( camera.up );
|
1696 |
+
this._upState.copy( camera.up );
|
1697 |
+
|
1698 |
+
this.object = camera;
|
1699 |
+
this.object.updateProjectionMatrix();
|
1700 |
+
|
1701 |
+
//making gizmos
|
1702 |
+
this._tbRadius = this.calculateTbRadius( camera );
|
1703 |
+
this.makeGizmos( this.target, this._tbRadius );
|
1704 |
+
|
1705 |
+
}
|
1706 |
+
|
1707 |
+
/**
|
1708 |
+
* Set gizmos visibility
|
1709 |
+
* @param {Boolean} value Value of gizmos visibility
|
1710 |
+
*/
|
1711 |
+
setGizmosVisible( value ) {
|
1712 |
+
|
1713 |
+
this._gizmos.visible = value;
|
1714 |
+
this.dispatchEvent( _changeEvent );
|
1715 |
+
|
1716 |
+
}
|
1717 |
+
|
1718 |
+
/**
|
1719 |
+
* Set gizmos radius factor and redraws gizmos
|
1720 |
+
* @param {Float} value Value of radius factor
|
1721 |
+
*/
|
1722 |
+
setTbRadius( value ) {
|
1723 |
+
|
1724 |
+
this.radiusFactor = value;
|
1725 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
1726 |
+
|
1727 |
+
const curve = new EllipseCurve( 0, 0, this._tbRadius, this._tbRadius );
|
1728 |
+
const points = curve.getPoints( this._curvePts );
|
1729 |
+
const curveGeometry = new BufferGeometry().setFromPoints( points );
|
1730 |
+
|
1731 |
+
|
1732 |
+
for ( const gizmo in this._gizmos.children ) {
|
1733 |
+
|
1734 |
+
this._gizmos.children[ gizmo ].geometry = curveGeometry;
|
1735 |
+
|
1736 |
+
}
|
1737 |
+
|
1738 |
+
this.dispatchEvent( _changeEvent );
|
1739 |
+
|
1740 |
+
}
|
1741 |
+
|
1742 |
+
/**
|
1743 |
+
* Creates the rotation gizmos matching trackball center and radius
|
1744 |
+
* @param {Vector3} tbCenter The trackball center
|
1745 |
+
* @param {number} tbRadius The trackball radius
|
1746 |
+
*/
|
1747 |
+
makeGizmos( tbCenter, tbRadius ) {
|
1748 |
+
|
1749 |
+
const curve = new EllipseCurve( 0, 0, tbRadius, tbRadius );
|
1750 |
+
const points = curve.getPoints( this._curvePts );
|
1751 |
+
|
1752 |
+
//geometry
|
1753 |
+
const curveGeometry = new BufferGeometry().setFromPoints( points );
|
1754 |
+
|
1755 |
+
//material
|
1756 |
+
const curveMaterialX = new LineBasicMaterial( { color: 0xff8080, fog: false, transparent: true, opacity: 0.6 } );
|
1757 |
+
const curveMaterialY = new LineBasicMaterial( { color: 0x80ff80, fog: false, transparent: true, opacity: 0.6 } );
|
1758 |
+
const curveMaterialZ = new LineBasicMaterial( { color: 0x8080ff, fog: false, transparent: true, opacity: 0.6 } );
|
1759 |
+
|
1760 |
+
//line
|
1761 |
+
const gizmoX = new Line( curveGeometry, curveMaterialX );
|
1762 |
+
const gizmoY = new Line( curveGeometry, curveMaterialY );
|
1763 |
+
const gizmoZ = new Line( curveGeometry, curveMaterialZ );
|
1764 |
+
|
1765 |
+
const rotation = Math.PI * 0.5;
|
1766 |
+
gizmoX.rotation.x = rotation;
|
1767 |
+
gizmoY.rotation.y = rotation;
|
1768 |
+
|
1769 |
+
|
1770 |
+
//setting state
|
1771 |
+
this._gizmoMatrixState0.identity().setPosition( tbCenter );
|
1772 |
+
this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
|
1773 |
+
|
1774 |
+
if ( this.object.zoom !== 1 ) {
|
1775 |
+
|
1776 |
+
//adapt gizmos size to camera zoom
|
1777 |
+
const size = 1 / this.object.zoom;
|
1778 |
+
this._scaleMatrix.makeScale( size, size, size );
|
1779 |
+
this._translationMatrix.makeTranslation( - tbCenter.x, - tbCenter.y, - tbCenter.z );
|
1780 |
+
|
1781 |
+
this._gizmoMatrixState.premultiply( this._translationMatrix ).premultiply( this._scaleMatrix );
|
1782 |
+
this._translationMatrix.makeTranslation( tbCenter.x, tbCenter.y, tbCenter.z );
|
1783 |
+
this._gizmoMatrixState.premultiply( this._translationMatrix );
|
1784 |
+
|
1785 |
+
}
|
1786 |
+
|
1787 |
+
this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
1788 |
+
|
1789 |
+
//
|
1790 |
+
|
1791 |
+
this._gizmos.traverse( function ( object ) {
|
1792 |
+
|
1793 |
+
if ( object.isLine ) {
|
1794 |
+
|
1795 |
+
object.geometry.dispose();
|
1796 |
+
object.material.dispose();
|
1797 |
+
|
1798 |
+
}
|
1799 |
+
|
1800 |
+
} );
|
1801 |
+
|
1802 |
+
this._gizmos.clear();
|
1803 |
+
|
1804 |
+
//
|
1805 |
+
|
1806 |
+
this._gizmos.add( gizmoX );
|
1807 |
+
this._gizmos.add( gizmoY );
|
1808 |
+
this._gizmos.add( gizmoZ );
|
1809 |
+
|
1810 |
+
}
|
1811 |
+
|
1812 |
+
/**
|
1813 |
+
* Perform animation for focus operation
|
1814 |
+
* @param {Number} time Instant in which this function is called as performance.now()
|
1815 |
+
* @param {Vector3} point Point of interest for focus operation
|
1816 |
+
* @param {Matrix4} cameraMatrix Camera matrix
|
1817 |
+
* @param {Matrix4} gizmoMatrix Gizmos matrix
|
1818 |
+
*/
|
1819 |
+
onFocusAnim( time, point, cameraMatrix, gizmoMatrix ) {
|
1820 |
+
|
1821 |
+
if ( this._timeStart == - 1 ) {
|
1822 |
+
|
1823 |
+
//animation start
|
1824 |
+
this._timeStart = time;
|
1825 |
+
|
1826 |
+
}
|
1827 |
+
|
1828 |
+
if ( this._state == STATE.ANIMATION_FOCUS ) {
|
1829 |
+
|
1830 |
+
const deltaTime = time - this._timeStart;
|
1831 |
+
const animTime = deltaTime / this.focusAnimationTime;
|
1832 |
+
|
1833 |
+
this._gizmoMatrixState.copy( gizmoMatrix );
|
1834 |
+
|
1835 |
+
if ( animTime >= 1 ) {
|
1836 |
+
|
1837 |
+
//animation end
|
1838 |
+
|
1839 |
+
this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
1840 |
+
|
1841 |
+
this.focus( point, this.scaleFactor );
|
1842 |
+
|
1843 |
+
this._timeStart = - 1;
|
1844 |
+
this.updateTbState( STATE.IDLE, false );
|
1845 |
+
this.activateGizmos( false );
|
1846 |
+
|
1847 |
+
this.dispatchEvent( _changeEvent );
|
1848 |
+
|
1849 |
+
} else {
|
1850 |
+
|
1851 |
+
const amount = this.easeOutCubic( animTime );
|
1852 |
+
const size = ( ( 1 - amount ) + ( this.scaleFactor * amount ) );
|
1853 |
+
|
1854 |
+
this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
1855 |
+
this.focus( point, size, amount );
|
1856 |
+
|
1857 |
+
this.dispatchEvent( _changeEvent );
|
1858 |
+
const self = this;
|
1859 |
+
this._animationId = window.requestAnimationFrame( function ( t ) {
|
1860 |
+
|
1861 |
+
self.onFocusAnim( t, point, cameraMatrix, gizmoMatrix.clone() );
|
1862 |
+
|
1863 |
+
} );
|
1864 |
+
|
1865 |
+
}
|
1866 |
+
|
1867 |
+
} else {
|
1868 |
+
|
1869 |
+
//interrupt animation
|
1870 |
+
|
1871 |
+
this._animationId = - 1;
|
1872 |
+
this._timeStart = - 1;
|
1873 |
+
|
1874 |
+
}
|
1875 |
+
|
1876 |
+
}
|
1877 |
+
|
1878 |
+
/**
|
1879 |
+
* Perform animation for rotation operation
|
1880 |
+
* @param {Number} time Instant in which this function is called as performance.now()
|
1881 |
+
* @param {Vector3} rotationAxis Rotation axis
|
1882 |
+
* @param {number} w0 Initial angular velocity
|
1883 |
+
*/
|
1884 |
+
onRotationAnim( time, rotationAxis, w0 ) {
|
1885 |
+
|
1886 |
+
if ( this._timeStart == - 1 ) {
|
1887 |
+
|
1888 |
+
//animation start
|
1889 |
+
this._anglePrev = 0;
|
1890 |
+
this._angleCurrent = 0;
|
1891 |
+
this._timeStart = time;
|
1892 |
+
|
1893 |
+
}
|
1894 |
+
|
1895 |
+
if ( this._state == STATE.ANIMATION_ROTATE ) {
|
1896 |
+
|
1897 |
+
//w = w0 + alpha * t
|
1898 |
+
const deltaTime = ( time - this._timeStart ) / 1000;
|
1899 |
+
const w = w0 + ( ( - this.dampingFactor ) * deltaTime );
|
1900 |
+
|
1901 |
+
if ( w > 0 ) {
|
1902 |
+
|
1903 |
+
//tetha = 0.5 * alpha * t^2 + w0 * t + tetha0
|
1904 |
+
this._angleCurrent = 0.5 * ( - this.dampingFactor ) * Math.pow( deltaTime, 2 ) + w0 * deltaTime + 0;
|
1905 |
+
this.applyTransformMatrix( this.rotate( rotationAxis, this._angleCurrent ) );
|
1906 |
+
this.dispatchEvent( _changeEvent );
|
1907 |
+
const self = this;
|
1908 |
+
this._animationId = window.requestAnimationFrame( function ( t ) {
|
1909 |
+
|
1910 |
+
self.onRotationAnim( t, rotationAxis, w0 );
|
1911 |
+
|
1912 |
+
} );
|
1913 |
+
|
1914 |
+
} else {
|
1915 |
+
|
1916 |
+
this._animationId = - 1;
|
1917 |
+
this._timeStart = - 1;
|
1918 |
+
|
1919 |
+
this.updateTbState( STATE.IDLE, false );
|
1920 |
+
this.activateGizmos( false );
|
1921 |
+
|
1922 |
+
this.dispatchEvent( _changeEvent );
|
1923 |
+
|
1924 |
+
}
|
1925 |
+
|
1926 |
+
} else {
|
1927 |
+
|
1928 |
+
//interrupt animation
|
1929 |
+
|
1930 |
+
this._animationId = - 1;
|
1931 |
+
this._timeStart = - 1;
|
1932 |
+
|
1933 |
+
if ( this._state != STATE.ROTATE ) {
|
1934 |
+
|
1935 |
+
this.activateGizmos( false );
|
1936 |
+
this.dispatchEvent( _changeEvent );
|
1937 |
+
|
1938 |
+
}
|
1939 |
+
|
1940 |
+
}
|
1941 |
+
|
1942 |
+
}
|
1943 |
+
|
1944 |
+
|
1945 |
+
/**
|
1946 |
+
* Perform pan operation moving camera between two points
|
1947 |
+
*
|
1948 |
+
* @param {Vector3} p0 Initial point
|
1949 |
+
* @param {Vector3} p1 Ending point
|
1950 |
+
* @param {Boolean} [adjust=false] If movement should be adjusted considering camera distance (Perspective only)
|
1951 |
+
* @returns {Object}
|
1952 |
+
*/
|
1953 |
+
pan( p0, p1, adjust = false ) {
|
1954 |
+
|
1955 |
+
const movement = p0.clone().sub( p1 );
|
1956 |
+
|
1957 |
+
if ( this.object.isOrthographicCamera ) {
|
1958 |
+
|
1959 |
+
//adjust movement amount
|
1960 |
+
movement.multiplyScalar( 1 / this.object.zoom );
|
1961 |
+
|
1962 |
+
} else if ( this.object.isPerspectiveCamera && adjust ) {
|
1963 |
+
|
1964 |
+
//adjust movement amount
|
1965 |
+
this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ); //camera's initial position
|
1966 |
+
this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ); //gizmo's initial position
|
1967 |
+
const distanceFactor = this._v3_1.distanceTo( this._v3_2 ) / this.object.position.distanceTo( this._gizmos.position );
|
1968 |
+
movement.multiplyScalar( 1 / distanceFactor );
|
1969 |
+
|
1970 |
+
}
|
1971 |
+
|
1972 |
+
this._v3_1.set( movement.x, movement.y, 0 ).applyQuaternion( this.object.quaternion );
|
1973 |
+
|
1974 |
+
this._m4_1.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z );
|
1975 |
+
|
1976 |
+
this.setTransformationMatrices( this._m4_1, this._m4_1 );
|
1977 |
+
return _transformation;
|
1978 |
+
|
1979 |
+
}
|
1980 |
+
|
1981 |
+
/**
|
1982 |
+
* Reset trackball
|
1983 |
+
*/
|
1984 |
+
reset() {
|
1985 |
+
|
1986 |
+
this.object.zoom = this._zoom0;
|
1987 |
+
|
1988 |
+
if ( this.object.isPerspectiveCamera ) {
|
1989 |
+
|
1990 |
+
this.object.fov = this._fov0;
|
1991 |
+
|
1992 |
+
}
|
1993 |
+
|
1994 |
+
this.object.near = this._nearPos;
|
1995 |
+
this.object.far = this._farPos;
|
1996 |
+
this._cameraMatrixState.copy( this._cameraMatrixState0 );
|
1997 |
+
this._cameraMatrixState.decompose( this.object.position, this.object.quaternion, this.object.scale );
|
1998 |
+
this.object.up.copy( this._up0 );
|
1999 |
+
|
2000 |
+
this.object.updateMatrix();
|
2001 |
+
this.object.updateProjectionMatrix();
|
2002 |
+
|
2003 |
+
this._gizmoMatrixState.copy( this._gizmoMatrixState0 );
|
2004 |
+
this._gizmoMatrixState0.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
2005 |
+
this._gizmos.updateMatrix();
|
2006 |
+
|
2007 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
2008 |
+
this.makeGizmos( this._gizmos.position, this._tbRadius );
|
2009 |
+
|
2010 |
+
this.object.lookAt( this._gizmos.position );
|
2011 |
+
|
2012 |
+
this.updateTbState( STATE.IDLE, false );
|
2013 |
+
|
2014 |
+
this.dispatchEvent( _changeEvent );
|
2015 |
+
|
2016 |
+
}
|
2017 |
+
|
2018 |
+
/**
|
2019 |
+
* Rotate the camera around an axis passing by trackball's center
|
2020 |
+
* @param {Vector3} axis Rotation axis
|
2021 |
+
* @param {number} angle Angle in radians
|
2022 |
+
* @returns {Object} Object with 'camera' field containing transformation matrix resulting from the operation to be applied to the camera
|
2023 |
+
*/
|
2024 |
+
rotate( axis, angle ) {
|
2025 |
+
|
2026 |
+
const point = this._gizmos.position; //rotation center
|
2027 |
+
this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
|
2028 |
+
this._rotationMatrix.makeRotationAxis( axis, - angle );
|
2029 |
+
|
2030 |
+
//rotate camera
|
2031 |
+
this._m4_1.makeTranslation( point.x, point.y, point.z );
|
2032 |
+
this._m4_1.multiply( this._rotationMatrix );
|
2033 |
+
this._m4_1.multiply( this._translationMatrix );
|
2034 |
+
|
2035 |
+
this.setTransformationMatrices( this._m4_1 );
|
2036 |
+
|
2037 |
+
return _transformation;
|
2038 |
+
|
2039 |
+
}
|
2040 |
+
|
2041 |
+
copyState() {
|
2042 |
+
|
2043 |
+
let state;
|
2044 |
+
if ( this.object.isOrthographicCamera ) {
|
2045 |
+
|
2046 |
+
state = JSON.stringify( {
|
2047 |
+
arcballState: {
|
2048 |
+
cameraFar: this.object.far,
|
2049 |
+
cameraMatrix: this.object.matrix,
|
2050 |
+
cameraNear: this.object.near,
|
2051 |
+
cameraUp: this.object.up,
|
2052 |
+
cameraZoom: this.object.zoom,
|
2053 |
+
gizmoMatrix: this._gizmos.matrix
|
2054 |
+
|
2055 |
+
}
|
2056 |
+
} );
|
2057 |
+
|
2058 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
2059 |
+
|
2060 |
+
state = JSON.stringify( {
|
2061 |
+
arcballState: {
|
2062 |
+
cameraFar: this.object.far,
|
2063 |
+
cameraFov: this.object.fov,
|
2064 |
+
cameraMatrix: this.object.matrix,
|
2065 |
+
cameraNear: this.object.near,
|
2066 |
+
cameraUp: this.object.up,
|
2067 |
+
cameraZoom: this.object.zoom,
|
2068 |
+
gizmoMatrix: this._gizmos.matrix
|
2069 |
+
|
2070 |
+
}
|
2071 |
+
} );
|
2072 |
+
|
2073 |
+
}
|
2074 |
+
|
2075 |
+
navigator.clipboard.writeText( state );
|
2076 |
+
|
2077 |
+
}
|
2078 |
+
|
2079 |
+
pasteState() {
|
2080 |
+
|
2081 |
+
const self = this;
|
2082 |
+
navigator.clipboard.readText().then( function resolved( value ) {
|
2083 |
+
|
2084 |
+
self.setStateFromJSON( value );
|
2085 |
+
|
2086 |
+
} );
|
2087 |
+
|
2088 |
+
}
|
2089 |
+
|
2090 |
+
/**
|
2091 |
+
* Save the current state of the control. This can later be recover with .reset
|
2092 |
+
*/
|
2093 |
+
saveState() {
|
2094 |
+
|
2095 |
+
this._cameraMatrixState0.copy( this.object.matrix );
|
2096 |
+
this._gizmoMatrixState0.copy( this._gizmos.matrix );
|
2097 |
+
this._nearPos = this.object.near;
|
2098 |
+
this._farPos = this.object.far;
|
2099 |
+
this._zoom0 = this.object.zoom;
|
2100 |
+
this._up0.copy( this.object.up );
|
2101 |
+
|
2102 |
+
if ( this.object.isPerspectiveCamera ) {
|
2103 |
+
|
2104 |
+
this._fov0 = this.object.fov;
|
2105 |
+
|
2106 |
+
}
|
2107 |
+
|
2108 |
+
}
|
2109 |
+
|
2110 |
+
/**
|
2111 |
+
* Perform uniform scale operation around a given point
|
2112 |
+
* @param {Number} size Scale factor
|
2113 |
+
* @param {Vector3} point Point around which scale
|
2114 |
+
* @param {Boolean} scaleGizmos If gizmos should be scaled (Perspective only)
|
2115 |
+
* @returns {Object} Object with 'camera' and 'gizmo' fields containing transformation matrices resulting from the operation to be applied to the camera and gizmos
|
2116 |
+
*/
|
2117 |
+
scale( size, point, scaleGizmos = true ) {
|
2118 |
+
|
2119 |
+
_scalePointTemp.copy( point );
|
2120 |
+
let sizeInverse = 1 / size;
|
2121 |
+
|
2122 |
+
if ( this.object.isOrthographicCamera ) {
|
2123 |
+
|
2124 |
+
//camera zoom
|
2125 |
+
this.object.zoom = this._zoomState;
|
2126 |
+
this.object.zoom *= size;
|
2127 |
+
|
2128 |
+
//check min and max zoom
|
2129 |
+
if ( this.object.zoom > this.maxZoom ) {
|
2130 |
+
|
2131 |
+
this.object.zoom = this.maxZoom;
|
2132 |
+
sizeInverse = this._zoomState / this.maxZoom;
|
2133 |
+
|
2134 |
+
} else if ( this.object.zoom < this.minZoom ) {
|
2135 |
+
|
2136 |
+
this.object.zoom = this.minZoom;
|
2137 |
+
sizeInverse = this._zoomState / this.minZoom;
|
2138 |
+
|
2139 |
+
}
|
2140 |
+
|
2141 |
+
this.object.updateProjectionMatrix();
|
2142 |
+
|
2143 |
+
this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ); //gizmos position
|
2144 |
+
|
2145 |
+
//scale gizmos so they appear in the same spot having the same dimension
|
2146 |
+
this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
|
2147 |
+
this._translationMatrix.makeTranslation( - this._v3_1.x, - this._v3_1.y, - this._v3_1.z );
|
2148 |
+
|
2149 |
+
this._m4_2.makeTranslation( this._v3_1.x, this._v3_1.y, this._v3_1.z ).multiply( this._scaleMatrix );
|
2150 |
+
this._m4_2.multiply( this._translationMatrix );
|
2151 |
+
|
2152 |
+
|
2153 |
+
//move camera and gizmos to obtain pinch effect
|
2154 |
+
_scalePointTemp.sub( this._v3_1 );
|
2155 |
+
|
2156 |
+
const amount = _scalePointTemp.clone().multiplyScalar( sizeInverse );
|
2157 |
+
_scalePointTemp.sub( amount );
|
2158 |
+
|
2159 |
+
this._m4_1.makeTranslation( _scalePointTemp.x, _scalePointTemp.y, _scalePointTemp.z );
|
2160 |
+
this._m4_2.premultiply( this._m4_1 );
|
2161 |
+
|
2162 |
+
this.setTransformationMatrices( this._m4_1, this._m4_2 );
|
2163 |
+
return _transformation;
|
2164 |
+
|
2165 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
2166 |
+
|
2167 |
+
this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
|
2168 |
+
this._v3_2.setFromMatrixPosition( this._gizmoMatrixState );
|
2169 |
+
|
2170 |
+
//move camera
|
2171 |
+
let distance = this._v3_1.distanceTo( _scalePointTemp );
|
2172 |
+
let amount = distance - ( distance * sizeInverse );
|
2173 |
+
|
2174 |
+
//check min and max distance
|
2175 |
+
const newDistance = distance - amount;
|
2176 |
+
if ( newDistance < this.minDistance ) {
|
2177 |
+
|
2178 |
+
sizeInverse = this.minDistance / distance;
|
2179 |
+
amount = distance - ( distance * sizeInverse );
|
2180 |
+
|
2181 |
+
} else if ( newDistance > this.maxDistance ) {
|
2182 |
+
|
2183 |
+
sizeInverse = this.maxDistance / distance;
|
2184 |
+
amount = distance - ( distance * sizeInverse );
|
2185 |
+
|
2186 |
+
}
|
2187 |
+
|
2188 |
+
_offset.copy( _scalePointTemp ).sub( this._v3_1 ).normalize().multiplyScalar( amount );
|
2189 |
+
|
2190 |
+
this._m4_1.makeTranslation( _offset.x, _offset.y, _offset.z );
|
2191 |
+
|
2192 |
+
|
2193 |
+
if ( scaleGizmos ) {
|
2194 |
+
|
2195 |
+
//scale gizmos so they appear in the same spot having the same dimension
|
2196 |
+
const pos = this._v3_2;
|
2197 |
+
|
2198 |
+
distance = pos.distanceTo( _scalePointTemp );
|
2199 |
+
amount = distance - ( distance * sizeInverse );
|
2200 |
+
_offset.copy( _scalePointTemp ).sub( this._v3_2 ).normalize().multiplyScalar( amount );
|
2201 |
+
|
2202 |
+
this._translationMatrix.makeTranslation( pos.x, pos.y, pos.z );
|
2203 |
+
this._scaleMatrix.makeScale( sizeInverse, sizeInverse, sizeInverse );
|
2204 |
+
|
2205 |
+
this._m4_2.makeTranslation( _offset.x, _offset.y, _offset.z ).multiply( this._translationMatrix );
|
2206 |
+
this._m4_2.multiply( this._scaleMatrix );
|
2207 |
+
|
2208 |
+
this._translationMatrix.makeTranslation( - pos.x, - pos.y, - pos.z );
|
2209 |
+
|
2210 |
+
this._m4_2.multiply( this._translationMatrix );
|
2211 |
+
this.setTransformationMatrices( this._m4_1, this._m4_2 );
|
2212 |
+
|
2213 |
+
|
2214 |
+
} else {
|
2215 |
+
|
2216 |
+
this.setTransformationMatrices( this._m4_1 );
|
2217 |
+
|
2218 |
+
}
|
2219 |
+
|
2220 |
+
return _transformation;
|
2221 |
+
|
2222 |
+
}
|
2223 |
+
|
2224 |
+
}
|
2225 |
+
|
2226 |
+
/**
|
2227 |
+
* Set camera fov
|
2228 |
+
* @param {Number} value fov to be set
|
2229 |
+
*/
|
2230 |
+
setFov( value ) {
|
2231 |
+
|
2232 |
+
if ( this.object.isPerspectiveCamera ) {
|
2233 |
+
|
2234 |
+
this.object.fov = MathUtils.clamp( value, this.minFov, this.maxFov );
|
2235 |
+
this.object.updateProjectionMatrix();
|
2236 |
+
|
2237 |
+
}
|
2238 |
+
|
2239 |
+
}
|
2240 |
+
|
2241 |
+
/**
|
2242 |
+
* Set values in transformation object
|
2243 |
+
*
|
2244 |
+
* @param {Matrix4} [camera=null] Transformation to be applied to the camera
|
2245 |
+
* @param {Matrix4} [gizmos=null] Transformation to be applied to gizmos
|
2246 |
+
*/
|
2247 |
+
setTransformationMatrices( camera = null, gizmos = null ) {
|
2248 |
+
|
2249 |
+
if ( camera != null ) {
|
2250 |
+
|
2251 |
+
if ( _transformation.camera != null ) {
|
2252 |
+
|
2253 |
+
_transformation.camera.copy( camera );
|
2254 |
+
|
2255 |
+
} else {
|
2256 |
+
|
2257 |
+
_transformation.camera = camera.clone();
|
2258 |
+
|
2259 |
+
}
|
2260 |
+
|
2261 |
+
} else {
|
2262 |
+
|
2263 |
+
_transformation.camera = null;
|
2264 |
+
|
2265 |
+
}
|
2266 |
+
|
2267 |
+
if ( gizmos != null ) {
|
2268 |
+
|
2269 |
+
if ( _transformation.gizmos != null ) {
|
2270 |
+
|
2271 |
+
_transformation.gizmos.copy( gizmos );
|
2272 |
+
|
2273 |
+
} else {
|
2274 |
+
|
2275 |
+
_transformation.gizmos = gizmos.clone();
|
2276 |
+
|
2277 |
+
}
|
2278 |
+
|
2279 |
+
} else {
|
2280 |
+
|
2281 |
+
_transformation.gizmos = null;
|
2282 |
+
|
2283 |
+
}
|
2284 |
+
|
2285 |
+
}
|
2286 |
+
|
2287 |
+
/**
|
2288 |
+
* Rotate camera around its direction axis passing by a given point by a given angle
|
2289 |
+
*
|
2290 |
+
* @param {Vector3} point The point where the rotation axis is passing trough
|
2291 |
+
* @param {Number} angle Angle in radians
|
2292 |
+
* @returns {Object} The computed transformation matrix
|
2293 |
+
*/
|
2294 |
+
zRotate( point, angle ) {
|
2295 |
+
|
2296 |
+
this._rotationMatrix.makeRotationAxis( this._rotationAxis, angle );
|
2297 |
+
this._translationMatrix.makeTranslation( - point.x, - point.y, - point.z );
|
2298 |
+
|
2299 |
+
this._m4_1.makeTranslation( point.x, point.y, point.z );
|
2300 |
+
this._m4_1.multiply( this._rotationMatrix );
|
2301 |
+
this._m4_1.multiply( this._translationMatrix );
|
2302 |
+
|
2303 |
+
this._v3_1.setFromMatrixPosition( this._gizmoMatrixState ).sub( point ); //vector from rotation center to gizmos position
|
2304 |
+
this._v3_2.copy( this._v3_1 ).applyAxisAngle( this._rotationAxis, angle ); //apply rotation
|
2305 |
+
this._v3_2.sub( this._v3_1 );
|
2306 |
+
|
2307 |
+
this._m4_2.makeTranslation( this._v3_2.x, this._v3_2.y, this._v3_2.z );
|
2308 |
+
|
2309 |
+
this.setTransformationMatrices( this._m4_1, this._m4_2 );
|
2310 |
+
return _transformation;
|
2311 |
+
|
2312 |
+
}
|
2313 |
+
|
2314 |
+
|
2315 |
+
getRaycaster() {
|
2316 |
+
|
2317 |
+
return _raycaster;
|
2318 |
+
|
2319 |
+
}
|
2320 |
+
|
2321 |
+
|
2322 |
+
/**
|
2323 |
+
* Unproject the cursor on the 3D object surface
|
2324 |
+
*
|
2325 |
+
* @param {Vector2} cursor Cursor coordinates in NDC
|
2326 |
+
* @param {Camera} camera Virtual camera
|
2327 |
+
* @returns {Vector3|null} The point of intersection with the model, if exist, null otherwise
|
2328 |
+
*/
|
2329 |
+
unprojectOnObj( cursor, camera ) {
|
2330 |
+
|
2331 |
+
const raycaster = this.getRaycaster();
|
2332 |
+
raycaster.near = camera.near;
|
2333 |
+
raycaster.far = camera.far;
|
2334 |
+
raycaster.setFromCamera( cursor, camera );
|
2335 |
+
|
2336 |
+
const intersect = raycaster.intersectObjects( this.scene.children, true );
|
2337 |
+
|
2338 |
+
for ( let i = 0; i < intersect.length; i ++ ) {
|
2339 |
+
|
2340 |
+
if ( intersect[ i ].object.uuid != this._gizmos.uuid && intersect[ i ].face != null ) {
|
2341 |
+
|
2342 |
+
return intersect[ i ].point.clone();
|
2343 |
+
|
2344 |
+
}
|
2345 |
+
|
2346 |
+
}
|
2347 |
+
|
2348 |
+
return null;
|
2349 |
+
|
2350 |
+
}
|
2351 |
+
|
2352 |
+
/**
|
2353 |
+
* Unproject the cursor on the trackball surface
|
2354 |
+
* @param {Camera} camera The virtual camera
|
2355 |
+
* @param {Number} cursorX Cursor horizontal coordinate on screen
|
2356 |
+
* @param {Number} cursorY Cursor vertical coordinate on screen
|
2357 |
+
* @param {HTMLElement} canvas The canvas where the renderer draws its output
|
2358 |
+
* @param {number} tbRadius The trackball radius
|
2359 |
+
* @returns {Vector3} The unprojected point on the trackball surface
|
2360 |
+
*/
|
2361 |
+
unprojectOnTbSurface( camera, cursorX, cursorY, canvas, tbRadius ) {
|
2362 |
+
|
2363 |
+
if ( camera.type == 'OrthographicCamera' ) {
|
2364 |
+
|
2365 |
+
this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
|
2366 |
+
this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
|
2367 |
+
|
2368 |
+
const x2 = Math.pow( this._v2_1.x, 2 );
|
2369 |
+
const y2 = Math.pow( this._v2_1.y, 2 );
|
2370 |
+
const r2 = Math.pow( this._tbRadius, 2 );
|
2371 |
+
|
2372 |
+
if ( x2 + y2 <= r2 * 0.5 ) {
|
2373 |
+
|
2374 |
+
//intersection with sphere
|
2375 |
+
this._v3_1.setZ( Math.sqrt( r2 - ( x2 + y2 ) ) );
|
2376 |
+
|
2377 |
+
} else {
|
2378 |
+
|
2379 |
+
//intersection with hyperboloid
|
2380 |
+
this._v3_1.setZ( ( r2 * 0.5 ) / ( Math.sqrt( x2 + y2 ) ) );
|
2381 |
+
|
2382 |
+
}
|
2383 |
+
|
2384 |
+
return this._v3_1;
|
2385 |
+
|
2386 |
+
} else if ( camera.type == 'PerspectiveCamera' ) {
|
2387 |
+
|
2388 |
+
//unproject cursor on the near plane
|
2389 |
+
this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
|
2390 |
+
|
2391 |
+
this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
|
2392 |
+
this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
|
2393 |
+
|
2394 |
+
const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
|
2395 |
+
const cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
|
2396 |
+
const radius2 = Math.pow( tbRadius, 2 );
|
2397 |
+
|
2398 |
+
// camera
|
2399 |
+
// |\
|
2400 |
+
// | \
|
2401 |
+
// | \
|
2402 |
+
// h | \
|
2403 |
+
// | \
|
2404 |
+
// | \
|
2405 |
+
// _ _ | _ _ _\ _ _ near plane
|
2406 |
+
// l
|
2407 |
+
|
2408 |
+
const h = this._v3_1.z;
|
2409 |
+
const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
|
2410 |
+
|
2411 |
+
if ( l == 0 ) {
|
2412 |
+
|
2413 |
+
//ray aligned with camera
|
2414 |
+
rayDir.set( this._v3_1.x, this._v3_1.y, tbRadius );
|
2415 |
+
return rayDir;
|
2416 |
+
|
2417 |
+
}
|
2418 |
+
|
2419 |
+
const m = h / l;
|
2420 |
+
const q = cameraGizmoDistance;
|
2421 |
+
|
2422 |
+
/*
|
2423 |
+
* calculate intersection point between unprojected ray and trackball surface
|
2424 |
+
*|y = m * x + q
|
2425 |
+
*|x^2 + y^2 = r^2
|
2426 |
+
*
|
2427 |
+
* (m^2 + 1) * x^2 + (2 * m * q) * x + q^2 - r^2 = 0
|
2428 |
+
*/
|
2429 |
+
let a = Math.pow( m, 2 ) + 1;
|
2430 |
+
let b = 2 * m * q;
|
2431 |
+
let c = Math.pow( q, 2 ) - radius2;
|
2432 |
+
let delta = Math.pow( b, 2 ) - ( 4 * a * c );
|
2433 |
+
|
2434 |
+
if ( delta >= 0 ) {
|
2435 |
+
|
2436 |
+
//intersection with sphere
|
2437 |
+
this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
|
2438 |
+
this._v2_1.setY( m * this._v2_1.x + q );
|
2439 |
+
|
2440 |
+
const angle = MathUtils.RAD2DEG * this._v2_1.angle();
|
2441 |
+
|
2442 |
+
if ( angle >= 45 ) {
|
2443 |
+
|
2444 |
+
//if angle between intersection point and X' axis is >= 45°, return that point
|
2445 |
+
//otherwise, calculate intersection point with hyperboloid
|
2446 |
+
|
2447 |
+
const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) );
|
2448 |
+
rayDir.multiplyScalar( rayLength );
|
2449 |
+
rayDir.z += cameraGizmoDistance;
|
2450 |
+
return rayDir;
|
2451 |
+
|
2452 |
+
}
|
2453 |
+
|
2454 |
+
}
|
2455 |
+
|
2456 |
+
//intersection with hyperboloid
|
2457 |
+
/*
|
2458 |
+
*|y = m * x + q
|
2459 |
+
*|y = (1 / x) * (r^2 / 2)
|
2460 |
+
*
|
2461 |
+
* m * x^2 + q * x - r^2 / 2 = 0
|
2462 |
+
*/
|
2463 |
+
|
2464 |
+
a = m;
|
2465 |
+
b = q;
|
2466 |
+
c = - radius2 * 0.5;
|
2467 |
+
delta = Math.pow( b, 2 ) - ( 4 * a * c );
|
2468 |
+
this._v2_1.setX( ( - b - Math.sqrt( delta ) ) / ( 2 * a ) );
|
2469 |
+
this._v2_1.setY( m * this._v2_1.x + q );
|
2470 |
+
|
2471 |
+
const rayLength = Math.sqrt( Math.pow( this._v2_1.x, 2 ) + Math.pow( ( cameraGizmoDistance - this._v2_1.y ), 2 ) );
|
2472 |
+
|
2473 |
+
rayDir.multiplyScalar( rayLength );
|
2474 |
+
rayDir.z += cameraGizmoDistance;
|
2475 |
+
return rayDir;
|
2476 |
+
|
2477 |
+
}
|
2478 |
+
|
2479 |
+
}
|
2480 |
+
|
2481 |
+
|
2482 |
+
/**
|
2483 |
+
* Unproject the cursor on the plane passing through the center of the trackball orthogonal to the camera
|
2484 |
+
* @param {Camera} camera The virtual camera
|
2485 |
+
* @param {Number} cursorX Cursor horizontal coordinate on screen
|
2486 |
+
* @param {Number} cursorY Cursor vertical coordinate on screen
|
2487 |
+
* @param {HTMLElement} canvas The canvas where the renderer draws its output
|
2488 |
+
* @param {Boolean} initialDistance If initial distance between camera and gizmos should be used for calculations instead of current (Perspective only)
|
2489 |
+
* @returns {Vector3} The unprojected point on the trackball plane
|
2490 |
+
*/
|
2491 |
+
unprojectOnTbPlane( camera, cursorX, cursorY, canvas, initialDistance = false ) {
|
2492 |
+
|
2493 |
+
if ( camera.type == 'OrthographicCamera' ) {
|
2494 |
+
|
2495 |
+
this._v2_1.copy( this.getCursorPosition( cursorX, cursorY, canvas ) );
|
2496 |
+
this._v3_1.set( this._v2_1.x, this._v2_1.y, 0 );
|
2497 |
+
|
2498 |
+
return this._v3_1.clone();
|
2499 |
+
|
2500 |
+
} else if ( camera.type == 'PerspectiveCamera' ) {
|
2501 |
+
|
2502 |
+
this._v2_1.copy( this.getCursorNDC( cursorX, cursorY, canvas ) );
|
2503 |
+
|
2504 |
+
//unproject cursor on the near plane
|
2505 |
+
this._v3_1.set( this._v2_1.x, this._v2_1.y, - 1 );
|
2506 |
+
this._v3_1.applyMatrix4( camera.projectionMatrixInverse );
|
2507 |
+
|
2508 |
+
const rayDir = this._v3_1.clone().normalize(); //unprojected ray direction
|
2509 |
+
|
2510 |
+
// camera
|
2511 |
+
// |\
|
2512 |
+
// | \
|
2513 |
+
// | \
|
2514 |
+
// h | \
|
2515 |
+
// | \
|
2516 |
+
// | \
|
2517 |
+
// _ _ | _ _ _\ _ _ near plane
|
2518 |
+
// l
|
2519 |
+
|
2520 |
+
const h = this._v3_1.z;
|
2521 |
+
const l = Math.sqrt( Math.pow( this._v3_1.x, 2 ) + Math.pow( this._v3_1.y, 2 ) );
|
2522 |
+
let cameraGizmoDistance;
|
2523 |
+
|
2524 |
+
if ( initialDistance ) {
|
2525 |
+
|
2526 |
+
cameraGizmoDistance = this._v3_1.setFromMatrixPosition( this._cameraMatrixState0 ).distanceTo( this._v3_2.setFromMatrixPosition( this._gizmoMatrixState0 ) );
|
2527 |
+
|
2528 |
+
} else {
|
2529 |
+
|
2530 |
+
cameraGizmoDistance = camera.position.distanceTo( this._gizmos.position );
|
2531 |
+
|
2532 |
+
}
|
2533 |
+
|
2534 |
+
/*
|
2535 |
+
* calculate intersection point between unprojected ray and the plane
|
2536 |
+
*|y = mx + q
|
2537 |
+
*|y = 0
|
2538 |
+
*
|
2539 |
+
* x = -q/m
|
2540 |
+
*/
|
2541 |
+
if ( l == 0 ) {
|
2542 |
+
|
2543 |
+
//ray aligned with camera
|
2544 |
+
rayDir.set( 0, 0, 0 );
|
2545 |
+
return rayDir;
|
2546 |
+
|
2547 |
+
}
|
2548 |
+
|
2549 |
+
const m = h / l;
|
2550 |
+
const q = cameraGizmoDistance;
|
2551 |
+
const x = - q / m;
|
2552 |
+
|
2553 |
+
const rayLength = Math.sqrt( Math.pow( q, 2 ) + Math.pow( x, 2 ) );
|
2554 |
+
rayDir.multiplyScalar( rayLength );
|
2555 |
+
rayDir.z = 0;
|
2556 |
+
return rayDir;
|
2557 |
+
|
2558 |
+
}
|
2559 |
+
|
2560 |
+
}
|
2561 |
+
|
2562 |
+
/**
|
2563 |
+
* Update camera and gizmos state
|
2564 |
+
*/
|
2565 |
+
updateMatrixState() {
|
2566 |
+
|
2567 |
+
//update camera and gizmos state
|
2568 |
+
this._cameraMatrixState.copy( this.object.matrix );
|
2569 |
+
this._gizmoMatrixState.copy( this._gizmos.matrix );
|
2570 |
+
|
2571 |
+
if ( this.object.isOrthographicCamera ) {
|
2572 |
+
|
2573 |
+
this._cameraProjectionState.copy( this.object.projectionMatrix );
|
2574 |
+
this.object.updateProjectionMatrix();
|
2575 |
+
this._zoomState = this.object.zoom;
|
2576 |
+
|
2577 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
2578 |
+
|
2579 |
+
this._fovState = this.object.fov;
|
2580 |
+
|
2581 |
+
}
|
2582 |
+
|
2583 |
+
}
|
2584 |
+
|
2585 |
+
/**
|
2586 |
+
* Update the trackball FSA
|
2587 |
+
* @param {STATE} newState New state of the FSA
|
2588 |
+
* @param {Boolean} updateMatrices If matrices state should be updated
|
2589 |
+
*/
|
2590 |
+
updateTbState( newState, updateMatrices ) {
|
2591 |
+
|
2592 |
+
this._state = newState;
|
2593 |
+
if ( updateMatrices ) {
|
2594 |
+
|
2595 |
+
this.updateMatrixState();
|
2596 |
+
|
2597 |
+
}
|
2598 |
+
|
2599 |
+
}
|
2600 |
+
|
2601 |
+
update() {
|
2602 |
+
|
2603 |
+
const EPS = 0.000001;
|
2604 |
+
|
2605 |
+
if ( this.target.equals( this._currentTarget ) === false ) {
|
2606 |
+
|
2607 |
+
this._gizmos.position.copy( this.target ); //for correct radius calculation
|
2608 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
2609 |
+
this.makeGizmos( this.target, this._tbRadius );
|
2610 |
+
this._currentTarget.copy( this.target );
|
2611 |
+
|
2612 |
+
}
|
2613 |
+
|
2614 |
+
//check min/max parameters
|
2615 |
+
if ( this.object.isOrthographicCamera ) {
|
2616 |
+
|
2617 |
+
//check zoom
|
2618 |
+
if ( this.object.zoom > this.maxZoom || this.object.zoom < this.minZoom ) {
|
2619 |
+
|
2620 |
+
const newZoom = MathUtils.clamp( this.object.zoom, this.minZoom, this.maxZoom );
|
2621 |
+
this.applyTransformMatrix( this.scale( newZoom / this.object.zoom, this._gizmos.position, true ) );
|
2622 |
+
|
2623 |
+
}
|
2624 |
+
|
2625 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
2626 |
+
|
2627 |
+
//check distance
|
2628 |
+
const distance = this.object.position.distanceTo( this._gizmos.position );
|
2629 |
+
|
2630 |
+
if ( distance > this.maxDistance + EPS || distance < this.minDistance - EPS ) {
|
2631 |
+
|
2632 |
+
const newDistance = MathUtils.clamp( distance, this.minDistance, this.maxDistance );
|
2633 |
+
this.applyTransformMatrix( this.scale( newDistance / distance, this._gizmos.position ) );
|
2634 |
+
this.updateMatrixState();
|
2635 |
+
|
2636 |
+
}
|
2637 |
+
|
2638 |
+
//check fov
|
2639 |
+
if ( this.object.fov < this.minFov || this.object.fov > this.maxFov ) {
|
2640 |
+
|
2641 |
+
this.object.fov = MathUtils.clamp( this.object.fov, this.minFov, this.maxFov );
|
2642 |
+
this.object.updateProjectionMatrix();
|
2643 |
+
|
2644 |
+
}
|
2645 |
+
|
2646 |
+
const oldRadius = this._tbRadius;
|
2647 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
2648 |
+
|
2649 |
+
if ( oldRadius < this._tbRadius - EPS || oldRadius > this._tbRadius + EPS ) {
|
2650 |
+
|
2651 |
+
const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
|
2652 |
+
const newRadius = this._tbRadius / scale;
|
2653 |
+
const curve = new EllipseCurve( 0, 0, newRadius, newRadius );
|
2654 |
+
const points = curve.getPoints( this._curvePts );
|
2655 |
+
const curveGeometry = new BufferGeometry().setFromPoints( points );
|
2656 |
+
|
2657 |
+
for ( const gizmo in this._gizmos.children ) {
|
2658 |
+
|
2659 |
+
this._gizmos.children[ gizmo ].geometry = curveGeometry;
|
2660 |
+
|
2661 |
+
}
|
2662 |
+
|
2663 |
+
}
|
2664 |
+
|
2665 |
+
}
|
2666 |
+
|
2667 |
+
this.object.lookAt( this._gizmos.position );
|
2668 |
+
|
2669 |
+
}
|
2670 |
+
|
2671 |
+
setStateFromJSON( json ) {
|
2672 |
+
|
2673 |
+
const state = JSON.parse( json );
|
2674 |
+
|
2675 |
+
if ( state.arcballState != undefined ) {
|
2676 |
+
|
2677 |
+
this._cameraMatrixState.fromArray( state.arcballState.cameraMatrix.elements );
|
2678 |
+
this._cameraMatrixState.decompose( this.object.position, this.object.quaternion, this.object.scale );
|
2679 |
+
|
2680 |
+
this.object.up.copy( state.arcballState.cameraUp );
|
2681 |
+
this.object.near = state.arcballState.cameraNear;
|
2682 |
+
this.object.far = state.arcballState.cameraFar;
|
2683 |
+
|
2684 |
+
this.object.zoom = state.arcballState.cameraZoom;
|
2685 |
+
|
2686 |
+
if ( this.object.isPerspectiveCamera ) {
|
2687 |
+
|
2688 |
+
this.object.fov = state.arcballState.cameraFov;
|
2689 |
+
|
2690 |
+
}
|
2691 |
+
|
2692 |
+
this._gizmoMatrixState.fromArray( state.arcballState.gizmoMatrix.elements );
|
2693 |
+
this._gizmoMatrixState.decompose( this._gizmos.position, this._gizmos.quaternion, this._gizmos.scale );
|
2694 |
+
|
2695 |
+
this.object.updateMatrix();
|
2696 |
+
this.object.updateProjectionMatrix();
|
2697 |
+
|
2698 |
+
this._gizmos.updateMatrix();
|
2699 |
+
|
2700 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
2701 |
+
const gizmoTmp = new Matrix4().copy( this._gizmoMatrixState0 );
|
2702 |
+
this.makeGizmos( this._gizmos.position, this._tbRadius );
|
2703 |
+
this._gizmoMatrixState0.copy( gizmoTmp );
|
2704 |
+
|
2705 |
+
this.object.lookAt( this._gizmos.position );
|
2706 |
+
this.updateTbState( STATE.IDLE, false );
|
2707 |
+
|
2708 |
+
this.dispatchEvent( _changeEvent );
|
2709 |
+
|
2710 |
+
}
|
2711 |
+
|
2712 |
+
}
|
2713 |
+
|
2714 |
+
}
|
2715 |
+
|
2716 |
+
//listeners
|
2717 |
+
|
2718 |
+
function onWindowResize() {
|
2719 |
+
|
2720 |
+
const scale = ( this._gizmos.scale.x + this._gizmos.scale.y + this._gizmos.scale.z ) / 3;
|
2721 |
+
this._tbRadius = this.calculateTbRadius( this.object );
|
2722 |
+
|
2723 |
+
const newRadius = this._tbRadius / scale;
|
2724 |
+
const curve = new EllipseCurve( 0, 0, newRadius, newRadius );
|
2725 |
+
const points = curve.getPoints( this._curvePts );
|
2726 |
+
const curveGeometry = new BufferGeometry().setFromPoints( points );
|
2727 |
+
|
2728 |
+
|
2729 |
+
for ( const gizmo in this._gizmos.children ) {
|
2730 |
+
|
2731 |
+
this._gizmos.children[ gizmo ].geometry = curveGeometry;
|
2732 |
+
|
2733 |
+
}
|
2734 |
+
|
2735 |
+
this.dispatchEvent( _changeEvent );
|
2736 |
+
|
2737 |
+
}
|
2738 |
+
|
2739 |
+
function onContextMenu( event ) {
|
2740 |
+
|
2741 |
+
if ( ! this.enabled ) {
|
2742 |
+
|
2743 |
+
return;
|
2744 |
+
|
2745 |
+
}
|
2746 |
+
|
2747 |
+
for ( let i = 0; i < this.mouseActions.length; i ++ ) {
|
2748 |
+
|
2749 |
+
if ( this.mouseActions[ i ].mouse == 2 ) {
|
2750 |
+
|
2751 |
+
//prevent only if button 2 is actually used
|
2752 |
+
event.preventDefault();
|
2753 |
+
break;
|
2754 |
+
|
2755 |
+
}
|
2756 |
+
|
2757 |
+
}
|
2758 |
+
|
2759 |
+
}
|
2760 |
+
|
2761 |
+
function onPointerCancel() {
|
2762 |
+
|
2763 |
+
this._touchStart.splice( 0, this._touchStart.length );
|
2764 |
+
this._touchCurrent.splice( 0, this._touchCurrent.length );
|
2765 |
+
this._input = INPUT.NONE;
|
2766 |
+
|
2767 |
+
}
|
2768 |
+
|
2769 |
+
function onPointerDown( event ) {
|
2770 |
+
|
2771 |
+
if ( event.button == 0 && event.isPrimary ) {
|
2772 |
+
|
2773 |
+
this._downValid = true;
|
2774 |
+
this._downEvents.push( event );
|
2775 |
+
this._downStart = performance.now();
|
2776 |
+
|
2777 |
+
} else {
|
2778 |
+
|
2779 |
+
this._downValid = false;
|
2780 |
+
|
2781 |
+
}
|
2782 |
+
|
2783 |
+
if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
|
2784 |
+
|
2785 |
+
this._touchStart.push( event );
|
2786 |
+
this._touchCurrent.push( event );
|
2787 |
+
|
2788 |
+
switch ( this._input ) {
|
2789 |
+
|
2790 |
+
case INPUT.NONE:
|
2791 |
+
|
2792 |
+
//singleStart
|
2793 |
+
this._input = INPUT.ONE_FINGER;
|
2794 |
+
this.onSinglePanStart( event, 'ROTATE' );
|
2795 |
+
|
2796 |
+
window.addEventListener( 'pointermove', this._onPointerMove );
|
2797 |
+
window.addEventListener( 'pointerup', this._onPointerUp );
|
2798 |
+
|
2799 |
+
break;
|
2800 |
+
|
2801 |
+
case INPUT.ONE_FINGER:
|
2802 |
+
case INPUT.ONE_FINGER_SWITCHED:
|
2803 |
+
|
2804 |
+
//doubleStart
|
2805 |
+
this._input = INPUT.TWO_FINGER;
|
2806 |
+
|
2807 |
+
this.onRotateStart();
|
2808 |
+
this.onPinchStart();
|
2809 |
+
this.onDoublePanStart();
|
2810 |
+
|
2811 |
+
break;
|
2812 |
+
|
2813 |
+
case INPUT.TWO_FINGER:
|
2814 |
+
|
2815 |
+
//multipleStart
|
2816 |
+
this._input = INPUT.MULT_FINGER;
|
2817 |
+
this.onTriplePanStart( event );
|
2818 |
+
break;
|
2819 |
+
|
2820 |
+
}
|
2821 |
+
|
2822 |
+
} else if ( event.pointerType != 'touch' && this._input == INPUT.NONE ) {
|
2823 |
+
|
2824 |
+
let modifier = null;
|
2825 |
+
|
2826 |
+
if ( event.ctrlKey || event.metaKey ) {
|
2827 |
+
|
2828 |
+
modifier = 'CTRL';
|
2829 |
+
|
2830 |
+
} else if ( event.shiftKey ) {
|
2831 |
+
|
2832 |
+
modifier = 'SHIFT';
|
2833 |
+
|
2834 |
+
}
|
2835 |
+
|
2836 |
+
this._mouseOp = this.getOpFromAction( event.button, modifier );
|
2837 |
+
if ( this._mouseOp != null ) {
|
2838 |
+
|
2839 |
+
window.addEventListener( 'pointermove', this._onPointerMove );
|
2840 |
+
window.addEventListener( 'pointerup', this._onPointerUp );
|
2841 |
+
|
2842 |
+
//singleStart
|
2843 |
+
this._input = INPUT.CURSOR;
|
2844 |
+
this._button = event.button;
|
2845 |
+
this.onSinglePanStart( event, this._mouseOp );
|
2846 |
+
|
2847 |
+
}
|
2848 |
+
|
2849 |
+
}
|
2850 |
+
|
2851 |
+
}
|
2852 |
+
|
2853 |
+
function onPointerMove( event ) {
|
2854 |
+
|
2855 |
+
if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
|
2856 |
+
|
2857 |
+
switch ( this._input ) {
|
2858 |
+
|
2859 |
+
case INPUT.ONE_FINGER:
|
2860 |
+
|
2861 |
+
//singleMove
|
2862 |
+
this.updateTouchEvent( event );
|
2863 |
+
|
2864 |
+
this.onSinglePanMove( event, STATE.ROTATE );
|
2865 |
+
break;
|
2866 |
+
|
2867 |
+
case INPUT.ONE_FINGER_SWITCHED:
|
2868 |
+
|
2869 |
+
const movement = this.calculatePointersDistance( this._touchCurrent[ 0 ], event ) * this._devPxRatio;
|
2870 |
+
|
2871 |
+
if ( movement >= this._switchSensibility ) {
|
2872 |
+
|
2873 |
+
//singleMove
|
2874 |
+
this._input = INPUT.ONE_FINGER;
|
2875 |
+
this.updateTouchEvent( event );
|
2876 |
+
|
2877 |
+
this.onSinglePanStart( event, 'ROTATE' );
|
2878 |
+
break;
|
2879 |
+
|
2880 |
+
}
|
2881 |
+
|
2882 |
+
break;
|
2883 |
+
|
2884 |
+
case INPUT.TWO_FINGER:
|
2885 |
+
|
2886 |
+
//rotate/pan/pinchMove
|
2887 |
+
this.updateTouchEvent( event );
|
2888 |
+
|
2889 |
+
this.onRotateMove();
|
2890 |
+
this.onPinchMove();
|
2891 |
+
this.onDoublePanMove();
|
2892 |
+
|
2893 |
+
break;
|
2894 |
+
|
2895 |
+
case INPUT.MULT_FINGER:
|
2896 |
+
|
2897 |
+
//multMove
|
2898 |
+
this.updateTouchEvent( event );
|
2899 |
+
|
2900 |
+
this.onTriplePanMove( event );
|
2901 |
+
break;
|
2902 |
+
|
2903 |
+
}
|
2904 |
+
|
2905 |
+
} else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
|
2906 |
+
|
2907 |
+
let modifier = null;
|
2908 |
+
|
2909 |
+
if ( event.ctrlKey || event.metaKey ) {
|
2910 |
+
|
2911 |
+
modifier = 'CTRL';
|
2912 |
+
|
2913 |
+
} else if ( event.shiftKey ) {
|
2914 |
+
|
2915 |
+
modifier = 'SHIFT';
|
2916 |
+
|
2917 |
+
}
|
2918 |
+
|
2919 |
+
const mouseOpState = this.getOpStateFromAction( this._button, modifier );
|
2920 |
+
|
2921 |
+
if ( mouseOpState != null ) {
|
2922 |
+
|
2923 |
+
this.onSinglePanMove( event, mouseOpState );
|
2924 |
+
|
2925 |
+
}
|
2926 |
+
|
2927 |
+
}
|
2928 |
+
|
2929 |
+
//checkDistance
|
2930 |
+
if ( this._downValid ) {
|
2931 |
+
|
2932 |
+
const movement = this.calculatePointersDistance( this._downEvents[ this._downEvents.length - 1 ], event ) * this._devPxRatio;
|
2933 |
+
if ( movement > this._movementThreshold ) {
|
2934 |
+
|
2935 |
+
this._downValid = false;
|
2936 |
+
|
2937 |
+
}
|
2938 |
+
|
2939 |
+
}
|
2940 |
+
|
2941 |
+
}
|
2942 |
+
|
2943 |
+
function onPointerUp( event ) {
|
2944 |
+
|
2945 |
+
if ( event.pointerType == 'touch' && this._input != INPUT.CURSOR ) {
|
2946 |
+
|
2947 |
+
const nTouch = this._touchCurrent.length;
|
2948 |
+
|
2949 |
+
for ( let i = 0; i < nTouch; i ++ ) {
|
2950 |
+
|
2951 |
+
if ( this._touchCurrent[ i ].pointerId == event.pointerId ) {
|
2952 |
+
|
2953 |
+
this._touchCurrent.splice( i, 1 );
|
2954 |
+
this._touchStart.splice( i, 1 );
|
2955 |
+
break;
|
2956 |
+
|
2957 |
+
}
|
2958 |
+
|
2959 |
+
}
|
2960 |
+
|
2961 |
+
switch ( this._input ) {
|
2962 |
+
|
2963 |
+
case INPUT.ONE_FINGER:
|
2964 |
+
case INPUT.ONE_FINGER_SWITCHED:
|
2965 |
+
|
2966 |
+
//singleEnd
|
2967 |
+
window.removeEventListener( 'pointermove', this._onPointerMove );
|
2968 |
+
window.removeEventListener( 'pointerup', this._onPointerUp );
|
2969 |
+
|
2970 |
+
this._input = INPUT.NONE;
|
2971 |
+
this.onSinglePanEnd();
|
2972 |
+
|
2973 |
+
break;
|
2974 |
+
|
2975 |
+
case INPUT.TWO_FINGER:
|
2976 |
+
|
2977 |
+
//doubleEnd
|
2978 |
+
this.onDoublePanEnd( event );
|
2979 |
+
this.onPinchEnd( event );
|
2980 |
+
this.onRotateEnd( event );
|
2981 |
+
|
2982 |
+
//switching to singleStart
|
2983 |
+
this._input = INPUT.ONE_FINGER_SWITCHED;
|
2984 |
+
|
2985 |
+
break;
|
2986 |
+
|
2987 |
+
case INPUT.MULT_FINGER:
|
2988 |
+
|
2989 |
+
if ( this._touchCurrent.length == 0 ) {
|
2990 |
+
|
2991 |
+
window.removeEventListener( 'pointermove', this._onPointerMove );
|
2992 |
+
window.removeEventListener( 'pointerup', this._onPointerUp );
|
2993 |
+
|
2994 |
+
//multCancel
|
2995 |
+
this._input = INPUT.NONE;
|
2996 |
+
this.onTriplePanEnd();
|
2997 |
+
|
2998 |
+
}
|
2999 |
+
|
3000 |
+
break;
|
3001 |
+
|
3002 |
+
}
|
3003 |
+
|
3004 |
+
} else if ( event.pointerType != 'touch' && this._input == INPUT.CURSOR ) {
|
3005 |
+
|
3006 |
+
window.removeEventListener( 'pointermove', this._onPointerMove );
|
3007 |
+
window.removeEventListener( 'pointerup', this._onPointerUp );
|
3008 |
+
|
3009 |
+
this._input = INPUT.NONE;
|
3010 |
+
this.onSinglePanEnd();
|
3011 |
+
this._button = - 1;
|
3012 |
+
|
3013 |
+
}
|
3014 |
+
|
3015 |
+
if ( event.isPrimary ) {
|
3016 |
+
|
3017 |
+
if ( this._downValid ) {
|
3018 |
+
|
3019 |
+
const downTime = event.timeStamp - this._downEvents[ this._downEvents.length - 1 ].timeStamp;
|
3020 |
+
|
3021 |
+
if ( downTime <= this._maxDownTime ) {
|
3022 |
+
|
3023 |
+
if ( this._nclicks == 0 ) {
|
3024 |
+
|
3025 |
+
//first valid click detected
|
3026 |
+
this._nclicks = 1;
|
3027 |
+
this._clickStart = performance.now();
|
3028 |
+
|
3029 |
+
} else {
|
3030 |
+
|
3031 |
+
const clickInterval = event.timeStamp - this._clickStart;
|
3032 |
+
const movement = this.calculatePointersDistance( this._downEvents[ 1 ], this._downEvents[ 0 ] ) * this._devPxRatio;
|
3033 |
+
|
3034 |
+
if ( clickInterval <= this._maxInterval && movement <= this._posThreshold ) {
|
3035 |
+
|
3036 |
+
//second valid click detected
|
3037 |
+
//fire double tap and reset values
|
3038 |
+
this._nclicks = 0;
|
3039 |
+
this._downEvents.splice( 0, this._downEvents.length );
|
3040 |
+
this.onDoubleTap( event );
|
3041 |
+
|
3042 |
+
} else {
|
3043 |
+
|
3044 |
+
//new 'first click'
|
3045 |
+
this._nclicks = 1;
|
3046 |
+
this._downEvents.shift();
|
3047 |
+
this._clickStart = performance.now();
|
3048 |
+
|
3049 |
+
}
|
3050 |
+
|
3051 |
+
}
|
3052 |
+
|
3053 |
+
} else {
|
3054 |
+
|
3055 |
+
this._downValid = false;
|
3056 |
+
this._nclicks = 0;
|
3057 |
+
this._downEvents.splice( 0, this._downEvents.length );
|
3058 |
+
|
3059 |
+
}
|
3060 |
+
|
3061 |
+
} else {
|
3062 |
+
|
3063 |
+
this._nclicks = 0;
|
3064 |
+
this._downEvents.splice( 0, this._downEvents.length );
|
3065 |
+
|
3066 |
+
}
|
3067 |
+
|
3068 |
+
}
|
3069 |
+
|
3070 |
+
}
|
3071 |
+
|
3072 |
+
function onWheel( event ) {
|
3073 |
+
|
3074 |
+
if ( this.enabled && this.enableZoom ) {
|
3075 |
+
|
3076 |
+
let modifier = null;
|
3077 |
+
|
3078 |
+
if ( event.ctrlKey || event.metaKey ) {
|
3079 |
+
|
3080 |
+
modifier = 'CTRL';
|
3081 |
+
|
3082 |
+
} else if ( event.shiftKey ) {
|
3083 |
+
|
3084 |
+
modifier = 'SHIFT';
|
3085 |
+
|
3086 |
+
}
|
3087 |
+
|
3088 |
+
const mouseOp = this.getOpFromAction( 'WHEEL', modifier );
|
3089 |
+
|
3090 |
+
if ( mouseOp != null ) {
|
3091 |
+
|
3092 |
+
event.preventDefault();
|
3093 |
+
this.dispatchEvent( _startEvent );
|
3094 |
+
|
3095 |
+
const notchDeltaY = 125; //distance of one notch of mouse wheel
|
3096 |
+
let sgn = event.deltaY / notchDeltaY;
|
3097 |
+
|
3098 |
+
let size = 1;
|
3099 |
+
|
3100 |
+
if ( sgn > 0 ) {
|
3101 |
+
|
3102 |
+
size = 1 / this.scaleFactor;
|
3103 |
+
|
3104 |
+
} else if ( sgn < 0 ) {
|
3105 |
+
|
3106 |
+
size = this.scaleFactor;
|
3107 |
+
|
3108 |
+
}
|
3109 |
+
|
3110 |
+
switch ( mouseOp ) {
|
3111 |
+
|
3112 |
+
case 'ZOOM':
|
3113 |
+
|
3114 |
+
this.updateTbState( STATE.SCALE, true );
|
3115 |
+
|
3116 |
+
if ( sgn > 0 ) {
|
3117 |
+
|
3118 |
+
size = 1 / ( Math.pow( this.scaleFactor, sgn ) );
|
3119 |
+
|
3120 |
+
} else if ( sgn < 0 ) {
|
3121 |
+
|
3122 |
+
size = Math.pow( this.scaleFactor, - sgn );
|
3123 |
+
|
3124 |
+
}
|
3125 |
+
|
3126 |
+
if ( this.cursorZoom && this.enablePan ) {
|
3127 |
+
|
3128 |
+
let scalePoint;
|
3129 |
+
|
3130 |
+
if ( this.object.isOrthographicCamera ) {
|
3131 |
+
|
3132 |
+
scalePoint = this.unprojectOnTbPlane( this.object, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.object.quaternion ).multiplyScalar( 1 / this.object.zoom ).add( this._gizmos.position );
|
3133 |
+
|
3134 |
+
} else if ( this.object.isPerspectiveCamera ) {
|
3135 |
+
|
3136 |
+
scalePoint = this.unprojectOnTbPlane( this.object, event.clientX, event.clientY, this.domElement ).applyQuaternion( this.object.quaternion ).add( this._gizmos.position );
|
3137 |
+
|
3138 |
+
}
|
3139 |
+
|
3140 |
+
this.applyTransformMatrix( this.scale( size, scalePoint ) );
|
3141 |
+
|
3142 |
+
} else {
|
3143 |
+
|
3144 |
+
this.applyTransformMatrix( this.scale( size, this._gizmos.position ) );
|
3145 |
+
|
3146 |
+
}
|
3147 |
+
|
3148 |
+
if ( this._grid != null ) {
|
3149 |
+
|
3150 |
+
this.disposeGrid();
|
3151 |
+
this.drawGrid();
|
3152 |
+
|
3153 |
+
}
|
3154 |
+
|
3155 |
+
this.updateTbState( STATE.IDLE, false );
|
3156 |
+
|
3157 |
+
this.dispatchEvent( _changeEvent );
|
3158 |
+
this.dispatchEvent( _endEvent );
|
3159 |
+
|
3160 |
+
break;
|
3161 |
+
|
3162 |
+
case 'FOV':
|
3163 |
+
|
3164 |
+
if ( this.object.isPerspectiveCamera ) {
|
3165 |
+
|
3166 |
+
this.updateTbState( STATE.FOV, true );
|
3167 |
+
|
3168 |
+
|
3169 |
+
//Vertigo effect
|
3170 |
+
|
3171 |
+
// fov / 2
|
3172 |
+
// |\
|
3173 |
+
// | \
|
3174 |
+
// | \
|
3175 |
+
// x | \
|
3176 |
+
// | \
|
3177 |
+
// | \
|
3178 |
+
// | _ _ _\
|
3179 |
+
// y
|
3180 |
+
|
3181 |
+
//check for iOs shift shortcut
|
3182 |
+
if ( event.deltaX != 0 ) {
|
3183 |
+
|
3184 |
+
sgn = event.deltaX / notchDeltaY;
|
3185 |
+
|
3186 |
+
size = 1;
|
3187 |
+
|
3188 |
+
if ( sgn > 0 ) {
|
3189 |
+
|
3190 |
+
size = 1 / ( Math.pow( this.scaleFactor, sgn ) );
|
3191 |
+
|
3192 |
+
} else if ( sgn < 0 ) {
|
3193 |
+
|
3194 |
+
size = Math.pow( this.scaleFactor, - sgn );
|
3195 |
+
|
3196 |
+
}
|
3197 |
+
|
3198 |
+
}
|
3199 |
+
|
3200 |
+
this._v3_1.setFromMatrixPosition( this._cameraMatrixState );
|
3201 |
+
const x = this._v3_1.distanceTo( this._gizmos.position );
|
3202 |
+
let xNew = x / size; //distance between camera and gizmos if scale(size, scalepoint) would be performed
|
3203 |
+
|
3204 |
+
//check min and max distance
|
3205 |
+
xNew = MathUtils.clamp( xNew, this.minDistance, this.maxDistance );
|
3206 |
+
|
3207 |
+
const y = x * Math.tan( MathUtils.DEG2RAD * this.object.fov * 0.5 );
|
3208 |
+
|
3209 |
+
//calculate new fov
|
3210 |
+
let newFov = MathUtils.RAD2DEG * ( Math.atan( y / xNew ) * 2 );
|
3211 |
+
|
3212 |
+
//check min and max fov
|
3213 |
+
if ( newFov > this.maxFov ) {
|
3214 |
+
|
3215 |
+
newFov = this.maxFov;
|
3216 |
+
|
3217 |
+
} else if ( newFov < this.minFov ) {
|
3218 |
+
|
3219 |
+
newFov = this.minFov;
|
3220 |
+
|
3221 |
+
}
|
3222 |
+
|
3223 |
+
const newDistance = y / Math.tan( MathUtils.DEG2RAD * ( newFov / 2 ) );
|
3224 |
+
size = x / newDistance;
|
3225 |
+
|
3226 |
+
this.setFov( newFov );
|
3227 |
+
this.applyTransformMatrix( this.scale( size, this._gizmos.position, false ) );
|
3228 |
+
|
3229 |
+
}
|
3230 |
+
|
3231 |
+
if ( this._grid != null ) {
|
3232 |
+
|
3233 |
+
this.disposeGrid();
|
3234 |
+
this.drawGrid();
|
3235 |
+
|
3236 |
+
}
|
3237 |
+
|
3238 |
+
this.updateTbState( STATE.IDLE, false );
|
3239 |
+
|
3240 |
+
this.dispatchEvent( _changeEvent );
|
3241 |
+
this.dispatchEvent( _endEvent );
|
3242 |
+
|
3243 |
+
break;
|
3244 |
+
|
3245 |
+
}
|
3246 |
+
|
3247 |
+
}
|
3248 |
+
|
3249 |
+
}
|
3250 |
+
|
3251 |
+
}
|
3252 |
+
|
3253 |
+
export { ArcballControls };
|
libs/three.js/0.172.0/jsm/controls/DragControls.js
ADDED
@@ -0,0 +1,410 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
Matrix4,
|
4 |
+
Plane,
|
5 |
+
Raycaster,
|
6 |
+
Vector2,
|
7 |
+
Vector3,
|
8 |
+
MOUSE,
|
9 |
+
TOUCH
|
10 |
+
} from 'three';
|
11 |
+
|
12 |
+
const _plane = new Plane();
|
13 |
+
|
14 |
+
const _pointer = new Vector2();
|
15 |
+
const _offset = new Vector3();
|
16 |
+
const _diff = new Vector2();
|
17 |
+
const _previousPointer = new Vector2();
|
18 |
+
const _intersection = new Vector3();
|
19 |
+
const _worldPosition = new Vector3();
|
20 |
+
const _inverseMatrix = new Matrix4();
|
21 |
+
|
22 |
+
const _up = new Vector3();
|
23 |
+
const _right = new Vector3();
|
24 |
+
|
25 |
+
let _selected = null, _hovered = null;
|
26 |
+
const _intersections = [];
|
27 |
+
|
28 |
+
const STATE = {
|
29 |
+
NONE: - 1,
|
30 |
+
PAN: 0,
|
31 |
+
ROTATE: 1
|
32 |
+
};
|
33 |
+
|
34 |
+
class DragControls extends Controls {
|
35 |
+
|
36 |
+
constructor( objects, camera, domElement = null ) {
|
37 |
+
|
38 |
+
super( camera, domElement );
|
39 |
+
|
40 |
+
this.objects = objects;
|
41 |
+
|
42 |
+
this.recursive = true;
|
43 |
+
this.transformGroup = false;
|
44 |
+
this.rotateSpeed = 1;
|
45 |
+
|
46 |
+
this.raycaster = new Raycaster();
|
47 |
+
|
48 |
+
// interaction
|
49 |
+
|
50 |
+
this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.PAN, RIGHT: MOUSE.ROTATE };
|
51 |
+
this.touches = { ONE: TOUCH.PAN };
|
52 |
+
|
53 |
+
// event listeners
|
54 |
+
|
55 |
+
this._onPointerMove = onPointerMove.bind( this );
|
56 |
+
this._onPointerDown = onPointerDown.bind( this );
|
57 |
+
this._onPointerCancel = onPointerCancel.bind( this );
|
58 |
+
this._onContextMenu = onContextMenu.bind( this );
|
59 |
+
|
60 |
+
//
|
61 |
+
|
62 |
+
if ( domElement !== null ) {
|
63 |
+
|
64 |
+
this.connect();
|
65 |
+
|
66 |
+
}
|
67 |
+
|
68 |
+
}
|
69 |
+
|
70 |
+
connect() {
|
71 |
+
|
72 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
73 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
74 |
+
this.domElement.addEventListener( 'pointerup', this._onPointerCancel );
|
75 |
+
this.domElement.addEventListener( 'pointerleave', this._onPointerCancel );
|
76 |
+
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
77 |
+
|
78 |
+
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
79 |
+
|
80 |
+
}
|
81 |
+
|
82 |
+
disconnect() {
|
83 |
+
|
84 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
85 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
86 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerCancel );
|
87 |
+
this.domElement.removeEventListener( 'pointerleave', this._onPointerCancel );
|
88 |
+
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
89 |
+
|
90 |
+
this.domElement.style.touchAction = 'auto';
|
91 |
+
this.domElement.style.cursor = '';
|
92 |
+
|
93 |
+
}
|
94 |
+
|
95 |
+
dispose() {
|
96 |
+
|
97 |
+
this.disconnect();
|
98 |
+
|
99 |
+
}
|
100 |
+
|
101 |
+
_updatePointer( event ) {
|
102 |
+
|
103 |
+
const rect = this.domElement.getBoundingClientRect();
|
104 |
+
|
105 |
+
_pointer.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
|
106 |
+
_pointer.y = - ( event.clientY - rect.top ) / rect.height * 2 + 1;
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
_updateState( event ) {
|
111 |
+
|
112 |
+
// determine action
|
113 |
+
|
114 |
+
let action;
|
115 |
+
|
116 |
+
if ( event.pointerType === 'touch' ) {
|
117 |
+
|
118 |
+
action = this.touches.ONE;
|
119 |
+
|
120 |
+
} else {
|
121 |
+
|
122 |
+
switch ( event.button ) {
|
123 |
+
|
124 |
+
case 0:
|
125 |
+
|
126 |
+
action = this.mouseButtons.LEFT;
|
127 |
+
break;
|
128 |
+
|
129 |
+
case 1:
|
130 |
+
|
131 |
+
action = this.mouseButtons.MIDDLE;
|
132 |
+
break;
|
133 |
+
|
134 |
+
case 2:
|
135 |
+
|
136 |
+
action = this.mouseButtons.RIGHT;
|
137 |
+
break;
|
138 |
+
|
139 |
+
default:
|
140 |
+
|
141 |
+
action = null;
|
142 |
+
|
143 |
+
}
|
144 |
+
|
145 |
+
}
|
146 |
+
|
147 |
+
// determine state
|
148 |
+
|
149 |
+
switch ( action ) {
|
150 |
+
|
151 |
+
case MOUSE.PAN:
|
152 |
+
case TOUCH.PAN:
|
153 |
+
|
154 |
+
this.state = STATE.PAN;
|
155 |
+
|
156 |
+
break;
|
157 |
+
|
158 |
+
case MOUSE.ROTATE:
|
159 |
+
case TOUCH.ROTATE:
|
160 |
+
|
161 |
+
this.state = STATE.ROTATE;
|
162 |
+
|
163 |
+
break;
|
164 |
+
|
165 |
+
default:
|
166 |
+
|
167 |
+
this.state = STATE.NONE;
|
168 |
+
|
169 |
+
}
|
170 |
+
|
171 |
+
}
|
172 |
+
|
173 |
+
getRaycaster() {
|
174 |
+
|
175 |
+
console.warn( 'THREE.DragControls: getRaycaster() has been deprecated. Use controls.raycaster instead.' ); // @deprecated r169
|
176 |
+
|
177 |
+
return this.raycaster;
|
178 |
+
|
179 |
+
}
|
180 |
+
|
181 |
+
setObjects( objects ) {
|
182 |
+
|
183 |
+
console.warn( 'THREE.DragControls: setObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
|
184 |
+
|
185 |
+
this.objects = objects;
|
186 |
+
|
187 |
+
}
|
188 |
+
|
189 |
+
getObjects() {
|
190 |
+
|
191 |
+
console.warn( 'THREE.DragControls: getObjects() has been deprecated. Use controls.objects instead.' ); // @deprecated r169
|
192 |
+
|
193 |
+
return this.objects;
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
activate() {
|
198 |
+
|
199 |
+
console.warn( 'THREE.DragControls: activate() has been renamed to connect().' ); // @deprecated r169
|
200 |
+
this.connect();
|
201 |
+
|
202 |
+
}
|
203 |
+
|
204 |
+
deactivate() {
|
205 |
+
|
206 |
+
console.warn( 'THREE.DragControls: deactivate() has been renamed to disconnect().' ); // @deprecated r169
|
207 |
+
this.disconnect();
|
208 |
+
|
209 |
+
}
|
210 |
+
|
211 |
+
set mode( value ) {
|
212 |
+
|
213 |
+
console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
|
214 |
+
|
215 |
+
}
|
216 |
+
|
217 |
+
get mode() {
|
218 |
+
|
219 |
+
console.warn( 'THREE.DragControls: The .mode property has been removed. Define the type of transformation via the .mouseButtons or .touches properties.' ); // @deprecated r169
|
220 |
+
|
221 |
+
}
|
222 |
+
|
223 |
+
}
|
224 |
+
|
225 |
+
function onPointerMove( event ) {
|
226 |
+
|
227 |
+
const camera = this.object;
|
228 |
+
const domElement = this.domElement;
|
229 |
+
const raycaster = this.raycaster;
|
230 |
+
|
231 |
+
if ( this.enabled === false ) return;
|
232 |
+
|
233 |
+
this._updatePointer( event );
|
234 |
+
|
235 |
+
raycaster.setFromCamera( _pointer, camera );
|
236 |
+
|
237 |
+
if ( _selected ) {
|
238 |
+
|
239 |
+
if ( this.state === STATE.PAN ) {
|
240 |
+
|
241 |
+
if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
|
242 |
+
|
243 |
+
_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
|
244 |
+
|
245 |
+
}
|
246 |
+
|
247 |
+
} else if ( this.state === STATE.ROTATE ) {
|
248 |
+
|
249 |
+
_diff.subVectors( _pointer, _previousPointer ).multiplyScalar( this.rotateSpeed );
|
250 |
+
_selected.rotateOnWorldAxis( _up, _diff.x );
|
251 |
+
_selected.rotateOnWorldAxis( _right.normalize(), - _diff.y );
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
this.dispatchEvent( { type: 'drag', object: _selected } );
|
256 |
+
|
257 |
+
_previousPointer.copy( _pointer );
|
258 |
+
|
259 |
+
} else {
|
260 |
+
|
261 |
+
// hover support
|
262 |
+
|
263 |
+
if ( event.pointerType === 'mouse' || event.pointerType === 'pen' ) {
|
264 |
+
|
265 |
+
_intersections.length = 0;
|
266 |
+
|
267 |
+
raycaster.setFromCamera( _pointer, camera );
|
268 |
+
raycaster.intersectObjects( this.objects, this.recursive, _intersections );
|
269 |
+
|
270 |
+
if ( _intersections.length > 0 ) {
|
271 |
+
|
272 |
+
const object = _intersections[ 0 ].object;
|
273 |
+
|
274 |
+
_plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
|
275 |
+
|
276 |
+
if ( _hovered !== object && _hovered !== null ) {
|
277 |
+
|
278 |
+
this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
|
279 |
+
|
280 |
+
domElement.style.cursor = 'auto';
|
281 |
+
_hovered = null;
|
282 |
+
|
283 |
+
}
|
284 |
+
|
285 |
+
if ( _hovered !== object ) {
|
286 |
+
|
287 |
+
this.dispatchEvent( { type: 'hoveron', object: object } );
|
288 |
+
|
289 |
+
domElement.style.cursor = 'pointer';
|
290 |
+
_hovered = object;
|
291 |
+
|
292 |
+
}
|
293 |
+
|
294 |
+
} else {
|
295 |
+
|
296 |
+
if ( _hovered !== null ) {
|
297 |
+
|
298 |
+
this.dispatchEvent( { type: 'hoveroff', object: _hovered } );
|
299 |
+
|
300 |
+
domElement.style.cursor = 'auto';
|
301 |
+
_hovered = null;
|
302 |
+
|
303 |
+
}
|
304 |
+
|
305 |
+
}
|
306 |
+
|
307 |
+
}
|
308 |
+
|
309 |
+
}
|
310 |
+
|
311 |
+
_previousPointer.copy( _pointer );
|
312 |
+
|
313 |
+
}
|
314 |
+
|
315 |
+
function onPointerDown( event ) {
|
316 |
+
|
317 |
+
const camera = this.object;
|
318 |
+
const domElement = this.domElement;
|
319 |
+
const raycaster = this.raycaster;
|
320 |
+
|
321 |
+
if ( this.enabled === false ) return;
|
322 |
+
|
323 |
+
this._updatePointer( event );
|
324 |
+
this._updateState( event );
|
325 |
+
|
326 |
+
_intersections.length = 0;
|
327 |
+
|
328 |
+
raycaster.setFromCamera( _pointer, camera );
|
329 |
+
raycaster.intersectObjects( this.objects, this.recursive, _intersections );
|
330 |
+
|
331 |
+
if ( _intersections.length > 0 ) {
|
332 |
+
|
333 |
+
if ( this.transformGroup === true ) {
|
334 |
+
|
335 |
+
// look for the outermost group in the object's upper hierarchy
|
336 |
+
|
337 |
+
_selected = findGroup( _intersections[ 0 ].object );
|
338 |
+
|
339 |
+
} else {
|
340 |
+
|
341 |
+
_selected = _intersections[ 0 ].object;
|
342 |
+
|
343 |
+
}
|
344 |
+
|
345 |
+
_plane.setFromNormalAndCoplanarPoint( camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
|
346 |
+
|
347 |
+
if ( raycaster.ray.intersectPlane( _plane, _intersection ) ) {
|
348 |
+
|
349 |
+
if ( this.state === STATE.PAN ) {
|
350 |
+
|
351 |
+
_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
|
352 |
+
_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
|
353 |
+
|
354 |
+
} else if ( this.state === STATE.ROTATE ) {
|
355 |
+
|
356 |
+
// the controls only support Y+ up
|
357 |
+
_up.set( 0, 1, 0 ).applyQuaternion( camera.quaternion ).normalize();
|
358 |
+
_right.set( 1, 0, 0 ).applyQuaternion( camera.quaternion ).normalize();
|
359 |
+
|
360 |
+
}
|
361 |
+
|
362 |
+
}
|
363 |
+
|
364 |
+
domElement.style.cursor = 'move';
|
365 |
+
|
366 |
+
this.dispatchEvent( { type: 'dragstart', object: _selected } );
|
367 |
+
|
368 |
+
}
|
369 |
+
|
370 |
+
_previousPointer.copy( _pointer );
|
371 |
+
|
372 |
+
}
|
373 |
+
|
374 |
+
function onPointerCancel() {
|
375 |
+
|
376 |
+
if ( this.enabled === false ) return;
|
377 |
+
|
378 |
+
if ( _selected ) {
|
379 |
+
|
380 |
+
this.dispatchEvent( { type: 'dragend', object: _selected } );
|
381 |
+
|
382 |
+
_selected = null;
|
383 |
+
|
384 |
+
}
|
385 |
+
|
386 |
+
this.domElement.style.cursor = _hovered ? 'pointer' : 'auto';
|
387 |
+
|
388 |
+
this.state = STATE.NONE;
|
389 |
+
|
390 |
+
}
|
391 |
+
|
392 |
+
function onContextMenu( event ) {
|
393 |
+
|
394 |
+
if ( this.enabled === false ) return;
|
395 |
+
|
396 |
+
event.preventDefault();
|
397 |
+
|
398 |
+
}
|
399 |
+
|
400 |
+
function findGroup( obj, group = null ) {
|
401 |
+
|
402 |
+
if ( obj.isGroup ) group = obj;
|
403 |
+
|
404 |
+
if ( obj.parent === null ) return group;
|
405 |
+
|
406 |
+
return findGroup( obj.parent, group );
|
407 |
+
|
408 |
+
}
|
409 |
+
|
410 |
+
export { DragControls };
|
libs/three.js/0.172.0/jsm/controls/FirstPersonControls.js
ADDED
@@ -0,0 +1,337 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
MathUtils,
|
4 |
+
Spherical,
|
5 |
+
Vector3
|
6 |
+
} from 'three';
|
7 |
+
|
8 |
+
const _lookDirection = new Vector3();
|
9 |
+
const _spherical = new Spherical();
|
10 |
+
const _target = new Vector3();
|
11 |
+
const _targetPosition = new Vector3();
|
12 |
+
|
13 |
+
class FirstPersonControls extends Controls {
|
14 |
+
|
15 |
+
constructor( object, domElement = null ) {
|
16 |
+
|
17 |
+
super( object, domElement );
|
18 |
+
|
19 |
+
// API
|
20 |
+
|
21 |
+
this.movementSpeed = 1.0;
|
22 |
+
this.lookSpeed = 0.005;
|
23 |
+
|
24 |
+
this.lookVertical = true;
|
25 |
+
this.autoForward = false;
|
26 |
+
|
27 |
+
this.activeLook = true;
|
28 |
+
|
29 |
+
this.heightSpeed = false;
|
30 |
+
this.heightCoef = 1.0;
|
31 |
+
this.heightMin = 0.0;
|
32 |
+
this.heightMax = 1.0;
|
33 |
+
|
34 |
+
this.constrainVertical = false;
|
35 |
+
this.verticalMin = 0;
|
36 |
+
this.verticalMax = Math.PI;
|
37 |
+
|
38 |
+
this.mouseDragOn = false;
|
39 |
+
|
40 |
+
// internals
|
41 |
+
|
42 |
+
this._autoSpeedFactor = 0.0;
|
43 |
+
|
44 |
+
this._pointerX = 0;
|
45 |
+
this._pointerY = 0;
|
46 |
+
|
47 |
+
this._moveForward = false;
|
48 |
+
this._moveBackward = false;
|
49 |
+
this._moveLeft = false;
|
50 |
+
this._moveRight = false;
|
51 |
+
|
52 |
+
this._viewHalfX = 0;
|
53 |
+
this._viewHalfY = 0;
|
54 |
+
|
55 |
+
this._lat = 0;
|
56 |
+
this._lon = 0;
|
57 |
+
|
58 |
+
// event listeners
|
59 |
+
|
60 |
+
this._onPointerMove = onPointerMove.bind( this );
|
61 |
+
this._onPointerDown = onPointerDown.bind( this );
|
62 |
+
this._onPointerUp = onPointerUp.bind( this );
|
63 |
+
this._onContextMenu = onContextMenu.bind( this );
|
64 |
+
this._onKeyDown = onKeyDown.bind( this );
|
65 |
+
this._onKeyUp = onKeyUp.bind( this );
|
66 |
+
|
67 |
+
//
|
68 |
+
|
69 |
+
if ( domElement !== null ) {
|
70 |
+
|
71 |
+
this.connect();
|
72 |
+
|
73 |
+
this.handleResize();
|
74 |
+
|
75 |
+
}
|
76 |
+
|
77 |
+
this._setOrientation();
|
78 |
+
|
79 |
+
}
|
80 |
+
|
81 |
+
connect() {
|
82 |
+
|
83 |
+
window.addEventListener( 'keydown', this._onKeyDown );
|
84 |
+
window.addEventListener( 'keyup', this._onKeyUp );
|
85 |
+
|
86 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
87 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
88 |
+
this.domElement.addEventListener( 'pointerup', this._onPointerUp );
|
89 |
+
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
90 |
+
|
91 |
+
}
|
92 |
+
|
93 |
+
disconnect() {
|
94 |
+
|
95 |
+
window.removeEventListener( 'keydown', this._onKeyDown );
|
96 |
+
window.removeEventListener( 'keyup', this._onKeyUp );
|
97 |
+
|
98 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerMove );
|
99 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerDown );
|
100 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
101 |
+
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
102 |
+
|
103 |
+
}
|
104 |
+
|
105 |
+
dispose() {
|
106 |
+
|
107 |
+
this.disconnect();
|
108 |
+
|
109 |
+
}
|
110 |
+
|
111 |
+
handleResize() {
|
112 |
+
|
113 |
+
if ( this.domElement === document ) {
|
114 |
+
|
115 |
+
this._viewHalfX = window.innerWidth / 2;
|
116 |
+
this._viewHalfY = window.innerHeight / 2;
|
117 |
+
|
118 |
+
} else {
|
119 |
+
|
120 |
+
this._viewHalfX = this.domElement.offsetWidth / 2;
|
121 |
+
this._viewHalfY = this.domElement.offsetHeight / 2;
|
122 |
+
|
123 |
+
}
|
124 |
+
|
125 |
+
}
|
126 |
+
|
127 |
+
lookAt( x, y, z ) {
|
128 |
+
|
129 |
+
if ( x.isVector3 ) {
|
130 |
+
|
131 |
+
_target.copy( x );
|
132 |
+
|
133 |
+
} else {
|
134 |
+
|
135 |
+
_target.set( x, y, z );
|
136 |
+
|
137 |
+
}
|
138 |
+
|
139 |
+
this.object.lookAt( _target );
|
140 |
+
|
141 |
+
this._setOrientation();
|
142 |
+
|
143 |
+
return this;
|
144 |
+
|
145 |
+
}
|
146 |
+
|
147 |
+
update( delta ) {
|
148 |
+
|
149 |
+
if ( this.enabled === false ) return;
|
150 |
+
|
151 |
+
if ( this.heightSpeed ) {
|
152 |
+
|
153 |
+
const y = MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
|
154 |
+
const heightDelta = y - this.heightMin;
|
155 |
+
|
156 |
+
this._autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
|
157 |
+
|
158 |
+
} else {
|
159 |
+
|
160 |
+
this._autoSpeedFactor = 0.0;
|
161 |
+
|
162 |
+
}
|
163 |
+
|
164 |
+
const actualMoveSpeed = delta * this.movementSpeed;
|
165 |
+
|
166 |
+
if ( this._moveForward || ( this.autoForward && ! this._moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this._autoSpeedFactor ) );
|
167 |
+
if ( this._moveBackward ) this.object.translateZ( actualMoveSpeed );
|
168 |
+
|
169 |
+
if ( this._moveLeft ) this.object.translateX( - actualMoveSpeed );
|
170 |
+
if ( this._moveRight ) this.object.translateX( actualMoveSpeed );
|
171 |
+
|
172 |
+
if ( this._moveUp ) this.object.translateY( actualMoveSpeed );
|
173 |
+
if ( this._moveDown ) this.object.translateY( - actualMoveSpeed );
|
174 |
+
|
175 |
+
let actualLookSpeed = delta * this.lookSpeed;
|
176 |
+
|
177 |
+
if ( ! this.activeLook ) {
|
178 |
+
|
179 |
+
actualLookSpeed = 0;
|
180 |
+
|
181 |
+
}
|
182 |
+
|
183 |
+
let verticalLookRatio = 1;
|
184 |
+
|
185 |
+
if ( this.constrainVertical ) {
|
186 |
+
|
187 |
+
verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
|
188 |
+
|
189 |
+
}
|
190 |
+
|
191 |
+
this._lon -= this._pointerX * actualLookSpeed;
|
192 |
+
if ( this.lookVertical ) this._lat -= this._pointerY * actualLookSpeed * verticalLookRatio;
|
193 |
+
|
194 |
+
this._lat = Math.max( - 85, Math.min( 85, this._lat ) );
|
195 |
+
|
196 |
+
let phi = MathUtils.degToRad( 90 - this._lat );
|
197 |
+
const theta = MathUtils.degToRad( this._lon );
|
198 |
+
|
199 |
+
if ( this.constrainVertical ) {
|
200 |
+
|
201 |
+
phi = MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
|
202 |
+
|
203 |
+
}
|
204 |
+
|
205 |
+
const position = this.object.position;
|
206 |
+
|
207 |
+
_targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
|
208 |
+
|
209 |
+
this.object.lookAt( _targetPosition );
|
210 |
+
|
211 |
+
}
|
212 |
+
|
213 |
+
_setOrientation() {
|
214 |
+
|
215 |
+
const quaternion = this.object.quaternion;
|
216 |
+
|
217 |
+
_lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion );
|
218 |
+
_spherical.setFromVector3( _lookDirection );
|
219 |
+
|
220 |
+
this._lat = 90 - MathUtils.radToDeg( _spherical.phi );
|
221 |
+
this._lon = MathUtils.radToDeg( _spherical.theta );
|
222 |
+
|
223 |
+
}
|
224 |
+
|
225 |
+
}
|
226 |
+
|
227 |
+
function onPointerDown( event ) {
|
228 |
+
|
229 |
+
if ( this.domElement !== document ) {
|
230 |
+
|
231 |
+
this.domElement.focus();
|
232 |
+
|
233 |
+
}
|
234 |
+
|
235 |
+
if ( this.activeLook ) {
|
236 |
+
|
237 |
+
switch ( event.button ) {
|
238 |
+
|
239 |
+
case 0: this._moveForward = true; break;
|
240 |
+
case 2: this._moveBackward = true; break;
|
241 |
+
|
242 |
+
}
|
243 |
+
|
244 |
+
}
|
245 |
+
|
246 |
+
this.mouseDragOn = true;
|
247 |
+
|
248 |
+
}
|
249 |
+
|
250 |
+
function onPointerUp( event ) {
|
251 |
+
|
252 |
+
if ( this.activeLook ) {
|
253 |
+
|
254 |
+
switch ( event.button ) {
|
255 |
+
|
256 |
+
case 0: this._moveForward = false; break;
|
257 |
+
case 2: this._moveBackward = false; break;
|
258 |
+
|
259 |
+
}
|
260 |
+
|
261 |
+
}
|
262 |
+
|
263 |
+
this.mouseDragOn = false;
|
264 |
+
|
265 |
+
}
|
266 |
+
|
267 |
+
function onPointerMove( event ) {
|
268 |
+
|
269 |
+
if ( this.domElement === document ) {
|
270 |
+
|
271 |
+
this._pointerX = event.pageX - this._viewHalfX;
|
272 |
+
this._pointerY = event.pageY - this._viewHalfY;
|
273 |
+
|
274 |
+
} else {
|
275 |
+
|
276 |
+
this._pointerX = event.pageX - this.domElement.offsetLeft - this._viewHalfX;
|
277 |
+
this._pointerY = event.pageY - this.domElement.offsetTop - this._viewHalfY;
|
278 |
+
|
279 |
+
}
|
280 |
+
|
281 |
+
}
|
282 |
+
|
283 |
+
function onKeyDown( event ) {
|
284 |
+
|
285 |
+
switch ( event.code ) {
|
286 |
+
|
287 |
+
case 'ArrowUp':
|
288 |
+
case 'KeyW': this._moveForward = true; break;
|
289 |
+
|
290 |
+
case 'ArrowLeft':
|
291 |
+
case 'KeyA': this._moveLeft = true; break;
|
292 |
+
|
293 |
+
case 'ArrowDown':
|
294 |
+
case 'KeyS': this._moveBackward = true; break;
|
295 |
+
|
296 |
+
case 'ArrowRight':
|
297 |
+
case 'KeyD': this._moveRight = true; break;
|
298 |
+
|
299 |
+
case 'KeyR': this._moveUp = true; break;
|
300 |
+
case 'KeyF': this._moveDown = true; break;
|
301 |
+
|
302 |
+
}
|
303 |
+
|
304 |
+
}
|
305 |
+
|
306 |
+
function onKeyUp( event ) {
|
307 |
+
|
308 |
+
switch ( event.code ) {
|
309 |
+
|
310 |
+
case 'ArrowUp':
|
311 |
+
case 'KeyW': this._moveForward = false; break;
|
312 |
+
|
313 |
+
case 'ArrowLeft':
|
314 |
+
case 'KeyA': this._moveLeft = false; break;
|
315 |
+
|
316 |
+
case 'ArrowDown':
|
317 |
+
case 'KeyS': this._moveBackward = false; break;
|
318 |
+
|
319 |
+
case 'ArrowRight':
|
320 |
+
case 'KeyD': this._moveRight = false; break;
|
321 |
+
|
322 |
+
case 'KeyR': this._moveUp = false; break;
|
323 |
+
case 'KeyF': this._moveDown = false; break;
|
324 |
+
|
325 |
+
}
|
326 |
+
|
327 |
+
}
|
328 |
+
|
329 |
+
function onContextMenu( event ) {
|
330 |
+
|
331 |
+
if ( this.enabled === false ) return;
|
332 |
+
|
333 |
+
event.preventDefault();
|
334 |
+
|
335 |
+
}
|
336 |
+
|
337 |
+
export { FirstPersonControls };
|
libs/three.js/0.172.0/jsm/controls/FlyControls.js
ADDED
@@ -0,0 +1,332 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
Quaternion,
|
4 |
+
Vector3
|
5 |
+
} from 'three';
|
6 |
+
|
7 |
+
const _changeEvent = { type: 'change' };
|
8 |
+
|
9 |
+
const _EPS = 0.000001;
|
10 |
+
const _tmpQuaternion = new Quaternion();
|
11 |
+
|
12 |
+
class FlyControls extends Controls {
|
13 |
+
|
14 |
+
constructor( object, domElement = null ) {
|
15 |
+
|
16 |
+
super( object, domElement );
|
17 |
+
|
18 |
+
this.movementSpeed = 1.0;
|
19 |
+
this.rollSpeed = 0.005;
|
20 |
+
|
21 |
+
this.dragToLook = false;
|
22 |
+
this.autoForward = false;
|
23 |
+
|
24 |
+
// internals
|
25 |
+
|
26 |
+
this._moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
|
27 |
+
this._moveVector = new Vector3( 0, 0, 0 );
|
28 |
+
this._rotationVector = new Vector3( 0, 0, 0 );
|
29 |
+
this._lastQuaternion = new Quaternion();
|
30 |
+
this._lastPosition = new Vector3();
|
31 |
+
this._status = 0;
|
32 |
+
|
33 |
+
// event listeners
|
34 |
+
|
35 |
+
this._onKeyDown = onKeyDown.bind( this );
|
36 |
+
this._onKeyUp = onKeyUp.bind( this );
|
37 |
+
this._onPointerMove = onPointerMove.bind( this );
|
38 |
+
this._onPointerDown = onPointerDown.bind( this );
|
39 |
+
this._onPointerUp = onPointerUp.bind( this );
|
40 |
+
this._onPointerCancel = onPointerCancel.bind( this );
|
41 |
+
this._onContextMenu = onContextMenu.bind( this );
|
42 |
+
|
43 |
+
//
|
44 |
+
|
45 |
+
if ( domElement !== null ) {
|
46 |
+
|
47 |
+
this.connect();
|
48 |
+
|
49 |
+
}
|
50 |
+
|
51 |
+
}
|
52 |
+
|
53 |
+
connect() {
|
54 |
+
|
55 |
+
window.addEventListener( 'keydown', this._onKeyDown );
|
56 |
+
window.addEventListener( 'keyup', this._onKeyUp );
|
57 |
+
|
58 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
59 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
60 |
+
this.domElement.addEventListener( 'pointerup', this._onPointerUp );
|
61 |
+
this.domElement.addEventListener( 'pointercancel', this._onPointerCancel );
|
62 |
+
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
63 |
+
|
64 |
+
}
|
65 |
+
|
66 |
+
disconnect() {
|
67 |
+
|
68 |
+
window.removeEventListener( 'keydown', this._onKeyDown );
|
69 |
+
window.removeEventListener( 'keyup', this._onKeyUp );
|
70 |
+
|
71 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
72 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
73 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
74 |
+
this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel );
|
75 |
+
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
76 |
+
|
77 |
+
}
|
78 |
+
|
79 |
+
dispose() {
|
80 |
+
|
81 |
+
this.disconnect();
|
82 |
+
|
83 |
+
}
|
84 |
+
|
85 |
+
update( delta ) {
|
86 |
+
|
87 |
+
if ( this.enabled === false ) return;
|
88 |
+
|
89 |
+
const object = this.object;
|
90 |
+
|
91 |
+
const moveMult = delta * this.movementSpeed;
|
92 |
+
const rotMult = delta * this.rollSpeed;
|
93 |
+
|
94 |
+
object.translateX( this._moveVector.x * moveMult );
|
95 |
+
object.translateY( this._moveVector.y * moveMult );
|
96 |
+
object.translateZ( this._moveVector.z * moveMult );
|
97 |
+
|
98 |
+
_tmpQuaternion.set( this._rotationVector.x * rotMult, this._rotationVector.y * rotMult, this._rotationVector.z * rotMult, 1 ).normalize();
|
99 |
+
object.quaternion.multiply( _tmpQuaternion );
|
100 |
+
|
101 |
+
if (
|
102 |
+
this._lastPosition.distanceToSquared( object.position ) > _EPS ||
|
103 |
+
8 * ( 1 - this._lastQuaternion.dot( object.quaternion ) ) > _EPS
|
104 |
+
) {
|
105 |
+
|
106 |
+
this.dispatchEvent( _changeEvent );
|
107 |
+
this._lastQuaternion.copy( object.quaternion );
|
108 |
+
this._lastPosition.copy( object.position );
|
109 |
+
|
110 |
+
}
|
111 |
+
|
112 |
+
}
|
113 |
+
|
114 |
+
// private
|
115 |
+
|
116 |
+
_updateMovementVector() {
|
117 |
+
|
118 |
+
const forward = ( this._moveState.forward || ( this.autoForward && ! this._moveState.back ) ) ? 1 : 0;
|
119 |
+
|
120 |
+
this._moveVector.x = ( - this._moveState.left + this._moveState.right );
|
121 |
+
this._moveVector.y = ( - this._moveState.down + this._moveState.up );
|
122 |
+
this._moveVector.z = ( - forward + this._moveState.back );
|
123 |
+
|
124 |
+
//console.log( 'move:', [ this._moveVector.x, this._moveVector.y, this._moveVector.z ] );
|
125 |
+
|
126 |
+
}
|
127 |
+
|
128 |
+
_updateRotationVector() {
|
129 |
+
|
130 |
+
this._rotationVector.x = ( - this._moveState.pitchDown + this._moveState.pitchUp );
|
131 |
+
this._rotationVector.y = ( - this._moveState.yawRight + this._moveState.yawLeft );
|
132 |
+
this._rotationVector.z = ( - this._moveState.rollRight + this._moveState.rollLeft );
|
133 |
+
|
134 |
+
//console.log( 'rotate:', [ this._rotationVector.x, this._rotationVector.y, this._rotationVector.z ] );
|
135 |
+
|
136 |
+
}
|
137 |
+
|
138 |
+
_getContainerDimensions() {
|
139 |
+
|
140 |
+
if ( this.domElement != document ) {
|
141 |
+
|
142 |
+
return {
|
143 |
+
size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
|
144 |
+
offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
|
145 |
+
};
|
146 |
+
|
147 |
+
} else {
|
148 |
+
|
149 |
+
return {
|
150 |
+
size: [ window.innerWidth, window.innerHeight ],
|
151 |
+
offset: [ 0, 0 ]
|
152 |
+
};
|
153 |
+
|
154 |
+
}
|
155 |
+
|
156 |
+
}
|
157 |
+
|
158 |
+
}
|
159 |
+
|
160 |
+
function onKeyDown( event ) {
|
161 |
+
|
162 |
+
if ( event.altKey || this.enabled === false ) {
|
163 |
+
|
164 |
+
return;
|
165 |
+
|
166 |
+
}
|
167 |
+
|
168 |
+
switch ( event.code ) {
|
169 |
+
|
170 |
+
case 'ShiftLeft':
|
171 |
+
case 'ShiftRight': this.movementSpeedMultiplier = .1; break;
|
172 |
+
|
173 |
+
case 'KeyW': this._moveState.forward = 1; break;
|
174 |
+
case 'KeyS': this._moveState.back = 1; break;
|
175 |
+
|
176 |
+
case 'KeyA': this._moveState.left = 1; break;
|
177 |
+
case 'KeyD': this._moveState.right = 1; break;
|
178 |
+
|
179 |
+
case 'KeyR': this._moveState.up = 1; break;
|
180 |
+
case 'KeyF': this._moveState.down = 1; break;
|
181 |
+
|
182 |
+
case 'ArrowUp': this._moveState.pitchUp = 1; break;
|
183 |
+
case 'ArrowDown': this._moveState.pitchDown = 1; break;
|
184 |
+
|
185 |
+
case 'ArrowLeft': this._moveState.yawLeft = 1; break;
|
186 |
+
case 'ArrowRight': this._moveState.yawRight = 1; break;
|
187 |
+
|
188 |
+
case 'KeyQ': this._moveState.rollLeft = 1; break;
|
189 |
+
case 'KeyE': this._moveState.rollRight = 1; break;
|
190 |
+
|
191 |
+
}
|
192 |
+
|
193 |
+
this._updateMovementVector();
|
194 |
+
this._updateRotationVector();
|
195 |
+
|
196 |
+
}
|
197 |
+
|
198 |
+
function onKeyUp( event ) {
|
199 |
+
|
200 |
+
if ( this.enabled === false ) return;
|
201 |
+
|
202 |
+
switch ( event.code ) {
|
203 |
+
|
204 |
+
case 'ShiftLeft':
|
205 |
+
case 'ShiftRight': this.movementSpeedMultiplier = 1; break;
|
206 |
+
|
207 |
+
case 'KeyW': this._moveState.forward = 0; break;
|
208 |
+
case 'KeyS': this._moveState.back = 0; break;
|
209 |
+
|
210 |
+
case 'KeyA': this._moveState.left = 0; break;
|
211 |
+
case 'KeyD': this._moveState.right = 0; break;
|
212 |
+
|
213 |
+
case 'KeyR': this._moveState.up = 0; break;
|
214 |
+
case 'KeyF': this._moveState.down = 0; break;
|
215 |
+
|
216 |
+
case 'ArrowUp': this._moveState.pitchUp = 0; break;
|
217 |
+
case 'ArrowDown': this._moveState.pitchDown = 0; break;
|
218 |
+
|
219 |
+
case 'ArrowLeft': this._moveState.yawLeft = 0; break;
|
220 |
+
case 'ArrowRight': this._moveState.yawRight = 0; break;
|
221 |
+
|
222 |
+
case 'KeyQ': this._moveState.rollLeft = 0; break;
|
223 |
+
case 'KeyE': this._moveState.rollRight = 0; break;
|
224 |
+
|
225 |
+
}
|
226 |
+
|
227 |
+
this._updateMovementVector();
|
228 |
+
this._updateRotationVector();
|
229 |
+
|
230 |
+
}
|
231 |
+
|
232 |
+
function onPointerDown( event ) {
|
233 |
+
|
234 |
+
if ( this.enabled === false ) return;
|
235 |
+
|
236 |
+
if ( this.dragToLook ) {
|
237 |
+
|
238 |
+
this._status ++;
|
239 |
+
|
240 |
+
} else {
|
241 |
+
|
242 |
+
switch ( event.button ) {
|
243 |
+
|
244 |
+
case 0: this._moveState.forward = 1; break;
|
245 |
+
case 2: this._moveState.back = 1; break;
|
246 |
+
|
247 |
+
}
|
248 |
+
|
249 |
+
this._updateMovementVector();
|
250 |
+
|
251 |
+
}
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
function onPointerMove( event ) {
|
256 |
+
|
257 |
+
if ( this.enabled === false ) return;
|
258 |
+
|
259 |
+
if ( ! this.dragToLook || this._status > 0 ) {
|
260 |
+
|
261 |
+
const container = this._getContainerDimensions();
|
262 |
+
const halfWidth = container.size[ 0 ] / 2;
|
263 |
+
const halfHeight = container.size[ 1 ] / 2;
|
264 |
+
|
265 |
+
this._moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
|
266 |
+
this._moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
|
267 |
+
|
268 |
+
this._updateRotationVector();
|
269 |
+
|
270 |
+
}
|
271 |
+
|
272 |
+
}
|
273 |
+
|
274 |
+
function onPointerUp( event ) {
|
275 |
+
|
276 |
+
if ( this.enabled === false ) return;
|
277 |
+
|
278 |
+
if ( this.dragToLook ) {
|
279 |
+
|
280 |
+
this._status --;
|
281 |
+
|
282 |
+
this._moveState.yawLeft = this._moveState.pitchDown = 0;
|
283 |
+
|
284 |
+
} else {
|
285 |
+
|
286 |
+
switch ( event.button ) {
|
287 |
+
|
288 |
+
case 0: this._moveState.forward = 0; break;
|
289 |
+
case 2: this._moveState.back = 0; break;
|
290 |
+
|
291 |
+
}
|
292 |
+
|
293 |
+
this._updateMovementVector();
|
294 |
+
|
295 |
+
}
|
296 |
+
|
297 |
+
this._updateRotationVector();
|
298 |
+
|
299 |
+
}
|
300 |
+
|
301 |
+
function onPointerCancel() {
|
302 |
+
|
303 |
+
if ( this.enabled === false ) return;
|
304 |
+
|
305 |
+
if ( this.dragToLook ) {
|
306 |
+
|
307 |
+
this._status = 0;
|
308 |
+
|
309 |
+
this._moveState.yawLeft = this._moveState.pitchDown = 0;
|
310 |
+
|
311 |
+
} else {
|
312 |
+
|
313 |
+
this._moveState.forward = 0;
|
314 |
+
this._moveState.back = 0;
|
315 |
+
|
316 |
+
this._updateMovementVector();
|
317 |
+
|
318 |
+
}
|
319 |
+
|
320 |
+
this._updateRotationVector();
|
321 |
+
|
322 |
+
}
|
323 |
+
|
324 |
+
function onContextMenu( event ) {
|
325 |
+
|
326 |
+
if ( this.enabled === false ) return;
|
327 |
+
|
328 |
+
event.preventDefault();
|
329 |
+
|
330 |
+
}
|
331 |
+
|
332 |
+
export { FlyControls };
|
libs/three.js/0.172.0/jsm/controls/MapControls.js
ADDED
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { MOUSE, TOUCH } from 'three';
|
2 |
+
|
3 |
+
import { OrbitControls } from './OrbitControls.js';
|
4 |
+
|
5 |
+
// MapControls performs orbiting, dollying (zooming), and panning.
|
6 |
+
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
7 |
+
//
|
8 |
+
// Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
|
9 |
+
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
10 |
+
// Pan - left mouse, or arrow keys / touch: one-finger move
|
11 |
+
|
12 |
+
class MapControls extends OrbitControls {
|
13 |
+
|
14 |
+
constructor( object, domElement ) {
|
15 |
+
|
16 |
+
super( object, domElement );
|
17 |
+
|
18 |
+
this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
|
19 |
+
|
20 |
+
this.mouseButtons = { LEFT: MOUSE.PAN, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.ROTATE };
|
21 |
+
|
22 |
+
this.touches = { ONE: TOUCH.PAN, TWO: TOUCH.DOLLY_ROTATE };
|
23 |
+
|
24 |
+
}
|
25 |
+
|
26 |
+
}
|
27 |
+
|
28 |
+
export { MapControls };
|
libs/three.js/0.172.0/jsm/controls/OrbitControls.js
ADDED
@@ -0,0 +1,1556 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
MOUSE,
|
4 |
+
Quaternion,
|
5 |
+
Spherical,
|
6 |
+
TOUCH,
|
7 |
+
Vector2,
|
8 |
+
Vector3,
|
9 |
+
Plane,
|
10 |
+
Ray,
|
11 |
+
MathUtils
|
12 |
+
} from 'three';
|
13 |
+
|
14 |
+
// OrbitControls performs orbiting, dollying (zooming), and panning.
|
15 |
+
// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
|
16 |
+
//
|
17 |
+
// Orbit - left mouse / touch: one-finger move
|
18 |
+
// Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
|
19 |
+
// Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
|
20 |
+
|
21 |
+
const _changeEvent = { type: 'change' };
|
22 |
+
const _startEvent = { type: 'start' };
|
23 |
+
const _endEvent = { type: 'end' };
|
24 |
+
const _ray = new Ray();
|
25 |
+
const _plane = new Plane();
|
26 |
+
const _TILT_LIMIT = Math.cos( 70 * MathUtils.DEG2RAD );
|
27 |
+
|
28 |
+
const _v = new Vector3();
|
29 |
+
const _twoPI = 2 * Math.PI;
|
30 |
+
|
31 |
+
const _STATE = {
|
32 |
+
NONE: - 1,
|
33 |
+
ROTATE: 0,
|
34 |
+
DOLLY: 1,
|
35 |
+
PAN: 2,
|
36 |
+
TOUCH_ROTATE: 3,
|
37 |
+
TOUCH_PAN: 4,
|
38 |
+
TOUCH_DOLLY_PAN: 5,
|
39 |
+
TOUCH_DOLLY_ROTATE: 6
|
40 |
+
};
|
41 |
+
const _EPS = 0.000001;
|
42 |
+
|
43 |
+
class OrbitControls extends Controls {
|
44 |
+
|
45 |
+
constructor( object, domElement = null ) {
|
46 |
+
|
47 |
+
super( object, domElement );
|
48 |
+
|
49 |
+
this.state = _STATE.NONE;
|
50 |
+
|
51 |
+
// Set to false to disable this control
|
52 |
+
this.enabled = true;
|
53 |
+
|
54 |
+
// "target" sets the location of focus, where the object orbits around
|
55 |
+
this.target = new Vector3();
|
56 |
+
|
57 |
+
// Sets the 3D cursor (similar to Blender), from which the maxTargetRadius takes effect
|
58 |
+
this.cursor = new Vector3();
|
59 |
+
|
60 |
+
// How far you can dolly in and out ( PerspectiveCamera only )
|
61 |
+
this.minDistance = 0;
|
62 |
+
this.maxDistance = Infinity;
|
63 |
+
|
64 |
+
// How far you can zoom in and out ( OrthographicCamera only )
|
65 |
+
this.minZoom = 0;
|
66 |
+
this.maxZoom = Infinity;
|
67 |
+
|
68 |
+
// Limit camera target within a spherical area around the cursor
|
69 |
+
this.minTargetRadius = 0;
|
70 |
+
this.maxTargetRadius = Infinity;
|
71 |
+
|
72 |
+
// How far you can orbit vertically, upper and lower limits.
|
73 |
+
// Range is 0 to Math.PI radians.
|
74 |
+
this.minPolarAngle = 0; // radians
|
75 |
+
this.maxPolarAngle = Math.PI; // radians
|
76 |
+
|
77 |
+
// How far you can orbit horizontally, upper and lower limits.
|
78 |
+
// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
|
79 |
+
this.minAzimuthAngle = - Infinity; // radians
|
80 |
+
this.maxAzimuthAngle = Infinity; // radians
|
81 |
+
|
82 |
+
// Set to true to enable damping (inertia)
|
83 |
+
// If damping is enabled, you must call controls.update() in your animation loop
|
84 |
+
this.enableDamping = false;
|
85 |
+
this.dampingFactor = 0.05;
|
86 |
+
|
87 |
+
// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
|
88 |
+
// Set to false to disable zooming
|
89 |
+
this.enableZoom = true;
|
90 |
+
this.zoomSpeed = 1.0;
|
91 |
+
|
92 |
+
// Set to false to disable rotating
|
93 |
+
this.enableRotate = true;
|
94 |
+
this.rotateSpeed = 1.0;
|
95 |
+
this.keyRotateSpeed = 1.0;
|
96 |
+
|
97 |
+
// Set to false to disable panning
|
98 |
+
this.enablePan = true;
|
99 |
+
this.panSpeed = 1.0;
|
100 |
+
this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
|
101 |
+
this.keyPanSpeed = 7.0; // pixels moved per arrow key push
|
102 |
+
this.zoomToCursor = false;
|
103 |
+
|
104 |
+
// Set to true to automatically rotate around the target
|
105 |
+
// If auto-rotate is enabled, you must call controls.update() in your animation loop
|
106 |
+
this.autoRotate = false;
|
107 |
+
this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
|
108 |
+
|
109 |
+
// The four arrow keys
|
110 |
+
this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
|
111 |
+
|
112 |
+
// Mouse buttons
|
113 |
+
this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
|
114 |
+
|
115 |
+
// Touch fingers
|
116 |
+
this.touches = { ONE: TOUCH.ROTATE, TWO: TOUCH.DOLLY_PAN };
|
117 |
+
|
118 |
+
// for reset
|
119 |
+
this.target0 = this.target.clone();
|
120 |
+
this.position0 = this.object.position.clone();
|
121 |
+
this.zoom0 = this.object.zoom;
|
122 |
+
|
123 |
+
// the target DOM element for key events
|
124 |
+
this._domElementKeyEvents = null;
|
125 |
+
|
126 |
+
// internals
|
127 |
+
|
128 |
+
this._lastPosition = new Vector3();
|
129 |
+
this._lastQuaternion = new Quaternion();
|
130 |
+
this._lastTargetPosition = new Vector3();
|
131 |
+
|
132 |
+
// so camera.up is the orbit axis
|
133 |
+
this._quat = new Quaternion().setFromUnitVectors( object.up, new Vector3( 0, 1, 0 ) );
|
134 |
+
this._quatInverse = this._quat.clone().invert();
|
135 |
+
|
136 |
+
// current position in spherical coordinates
|
137 |
+
this._spherical = new Spherical();
|
138 |
+
this._sphericalDelta = new Spherical();
|
139 |
+
|
140 |
+
this._scale = 1;
|
141 |
+
this._panOffset = new Vector3();
|
142 |
+
|
143 |
+
this._rotateStart = new Vector2();
|
144 |
+
this._rotateEnd = new Vector2();
|
145 |
+
this._rotateDelta = new Vector2();
|
146 |
+
|
147 |
+
this._panStart = new Vector2();
|
148 |
+
this._panEnd = new Vector2();
|
149 |
+
this._panDelta = new Vector2();
|
150 |
+
|
151 |
+
this._dollyStart = new Vector2();
|
152 |
+
this._dollyEnd = new Vector2();
|
153 |
+
this._dollyDelta = new Vector2();
|
154 |
+
|
155 |
+
this._dollyDirection = new Vector3();
|
156 |
+
this._mouse = new Vector2();
|
157 |
+
this._performCursorZoom = false;
|
158 |
+
|
159 |
+
this._pointers = [];
|
160 |
+
this._pointerPositions = {};
|
161 |
+
|
162 |
+
this._controlActive = false;
|
163 |
+
|
164 |
+
// event listeners
|
165 |
+
|
166 |
+
this._onPointerMove = onPointerMove.bind( this );
|
167 |
+
this._onPointerDown = onPointerDown.bind( this );
|
168 |
+
this._onPointerUp = onPointerUp.bind( this );
|
169 |
+
this._onContextMenu = onContextMenu.bind( this );
|
170 |
+
this._onMouseWheel = onMouseWheel.bind( this );
|
171 |
+
this._onKeyDown = onKeyDown.bind( this );
|
172 |
+
|
173 |
+
this._onTouchStart = onTouchStart.bind( this );
|
174 |
+
this._onTouchMove = onTouchMove.bind( this );
|
175 |
+
|
176 |
+
this._onMouseDown = onMouseDown.bind( this );
|
177 |
+
this._onMouseMove = onMouseMove.bind( this );
|
178 |
+
|
179 |
+
this._interceptControlDown = interceptControlDown.bind( this );
|
180 |
+
this._interceptControlUp = interceptControlUp.bind( this );
|
181 |
+
|
182 |
+
//
|
183 |
+
|
184 |
+
if ( this.domElement !== null ) {
|
185 |
+
|
186 |
+
this.connect();
|
187 |
+
|
188 |
+
}
|
189 |
+
|
190 |
+
this.update();
|
191 |
+
|
192 |
+
}
|
193 |
+
|
194 |
+
connect() {
|
195 |
+
|
196 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
197 |
+
this.domElement.addEventListener( 'pointercancel', this._onPointerUp );
|
198 |
+
|
199 |
+
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
200 |
+
this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } );
|
201 |
+
|
202 |
+
const document = this.domElement.getRootNode(); // offscreen canvas compatibility
|
203 |
+
document.addEventListener( 'keydown', this._interceptControlDown, { passive: true, capture: true } );
|
204 |
+
|
205 |
+
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
206 |
+
|
207 |
+
}
|
208 |
+
|
209 |
+
disconnect() {
|
210 |
+
|
211 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
212 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
213 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
214 |
+
this.domElement.removeEventListener( 'pointercancel', this._onPointerUp );
|
215 |
+
|
216 |
+
this.domElement.removeEventListener( 'wheel', this._onMouseWheel );
|
217 |
+
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
218 |
+
|
219 |
+
this.stopListenToKeyEvents();
|
220 |
+
|
221 |
+
const document = this.domElement.getRootNode(); // offscreen canvas compatibility
|
222 |
+
document.removeEventListener( 'keydown', this._interceptControlDown, { capture: true } );
|
223 |
+
|
224 |
+
this.domElement.style.touchAction = 'auto';
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
dispose() {
|
229 |
+
|
230 |
+
this.disconnect();
|
231 |
+
|
232 |
+
}
|
233 |
+
|
234 |
+
getPolarAngle() {
|
235 |
+
|
236 |
+
return this._spherical.phi;
|
237 |
+
|
238 |
+
}
|
239 |
+
|
240 |
+
getAzimuthalAngle() {
|
241 |
+
|
242 |
+
return this._spherical.theta;
|
243 |
+
|
244 |
+
}
|
245 |
+
|
246 |
+
getDistance() {
|
247 |
+
|
248 |
+
return this.object.position.distanceTo( this.target );
|
249 |
+
|
250 |
+
}
|
251 |
+
|
252 |
+
listenToKeyEvents( domElement ) {
|
253 |
+
|
254 |
+
domElement.addEventListener( 'keydown', this._onKeyDown );
|
255 |
+
this._domElementKeyEvents = domElement;
|
256 |
+
|
257 |
+
}
|
258 |
+
|
259 |
+
stopListenToKeyEvents() {
|
260 |
+
|
261 |
+
if ( this._domElementKeyEvents !== null ) {
|
262 |
+
|
263 |
+
this._domElementKeyEvents.removeEventListener( 'keydown', this._onKeyDown );
|
264 |
+
this._domElementKeyEvents = null;
|
265 |
+
|
266 |
+
}
|
267 |
+
|
268 |
+
}
|
269 |
+
|
270 |
+
saveState() {
|
271 |
+
|
272 |
+
this.target0.copy( this.target );
|
273 |
+
this.position0.copy( this.object.position );
|
274 |
+
this.zoom0 = this.object.zoom;
|
275 |
+
|
276 |
+
}
|
277 |
+
|
278 |
+
reset() {
|
279 |
+
|
280 |
+
this.target.copy( this.target0 );
|
281 |
+
this.object.position.copy( this.position0 );
|
282 |
+
this.object.zoom = this.zoom0;
|
283 |
+
|
284 |
+
this.object.updateProjectionMatrix();
|
285 |
+
this.dispatchEvent( _changeEvent );
|
286 |
+
|
287 |
+
this.update();
|
288 |
+
|
289 |
+
this.state = _STATE.NONE;
|
290 |
+
|
291 |
+
}
|
292 |
+
|
293 |
+
update( deltaTime = null ) {
|
294 |
+
|
295 |
+
const position = this.object.position;
|
296 |
+
|
297 |
+
_v.copy( position ).sub( this.target );
|
298 |
+
|
299 |
+
// rotate offset to "y-axis-is-up" space
|
300 |
+
_v.applyQuaternion( this._quat );
|
301 |
+
|
302 |
+
// angle from z-axis around y-axis
|
303 |
+
this._spherical.setFromVector3( _v );
|
304 |
+
|
305 |
+
if ( this.autoRotate && this.state === _STATE.NONE ) {
|
306 |
+
|
307 |
+
this._rotateLeft( this._getAutoRotationAngle( deltaTime ) );
|
308 |
+
|
309 |
+
}
|
310 |
+
|
311 |
+
if ( this.enableDamping ) {
|
312 |
+
|
313 |
+
this._spherical.theta += this._sphericalDelta.theta * this.dampingFactor;
|
314 |
+
this._spherical.phi += this._sphericalDelta.phi * this.dampingFactor;
|
315 |
+
|
316 |
+
} else {
|
317 |
+
|
318 |
+
this._spherical.theta += this._sphericalDelta.theta;
|
319 |
+
this._spherical.phi += this._sphericalDelta.phi;
|
320 |
+
|
321 |
+
}
|
322 |
+
|
323 |
+
// restrict theta to be between desired limits
|
324 |
+
|
325 |
+
let min = this.minAzimuthAngle;
|
326 |
+
let max = this.maxAzimuthAngle;
|
327 |
+
|
328 |
+
if ( isFinite( min ) && isFinite( max ) ) {
|
329 |
+
|
330 |
+
if ( min < - Math.PI ) min += _twoPI; else if ( min > Math.PI ) min -= _twoPI;
|
331 |
+
|
332 |
+
if ( max < - Math.PI ) max += _twoPI; else if ( max > Math.PI ) max -= _twoPI;
|
333 |
+
|
334 |
+
if ( min <= max ) {
|
335 |
+
|
336 |
+
this._spherical.theta = Math.max( min, Math.min( max, this._spherical.theta ) );
|
337 |
+
|
338 |
+
} else {
|
339 |
+
|
340 |
+
this._spherical.theta = ( this._spherical.theta > ( min + max ) / 2 ) ?
|
341 |
+
Math.max( min, this._spherical.theta ) :
|
342 |
+
Math.min( max, this._spherical.theta );
|
343 |
+
|
344 |
+
}
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
// restrict phi to be between desired limits
|
349 |
+
this._spherical.phi = Math.max( this.minPolarAngle, Math.min( this.maxPolarAngle, this._spherical.phi ) );
|
350 |
+
|
351 |
+
this._spherical.makeSafe();
|
352 |
+
|
353 |
+
|
354 |
+
// move target to panned location
|
355 |
+
|
356 |
+
if ( this.enableDamping === true ) {
|
357 |
+
|
358 |
+
this.target.addScaledVector( this._panOffset, this.dampingFactor );
|
359 |
+
|
360 |
+
} else {
|
361 |
+
|
362 |
+
this.target.add( this._panOffset );
|
363 |
+
|
364 |
+
}
|
365 |
+
|
366 |
+
// Limit the target distance from the cursor to create a sphere around the center of interest
|
367 |
+
this.target.sub( this.cursor );
|
368 |
+
this.target.clampLength( this.minTargetRadius, this.maxTargetRadius );
|
369 |
+
this.target.add( this.cursor );
|
370 |
+
|
371 |
+
let zoomChanged = false;
|
372 |
+
// adjust the camera position based on zoom only if we're not zooming to the cursor or if it's an ortho camera
|
373 |
+
// we adjust zoom later in these cases
|
374 |
+
if ( this.zoomToCursor && this._performCursorZoom || this.object.isOrthographicCamera ) {
|
375 |
+
|
376 |
+
this._spherical.radius = this._clampDistance( this._spherical.radius );
|
377 |
+
|
378 |
+
} else {
|
379 |
+
|
380 |
+
const prevRadius = this._spherical.radius;
|
381 |
+
this._spherical.radius = this._clampDistance( this._spherical.radius * this._scale );
|
382 |
+
zoomChanged = prevRadius != this._spherical.radius;
|
383 |
+
|
384 |
+
}
|
385 |
+
|
386 |
+
_v.setFromSpherical( this._spherical );
|
387 |
+
|
388 |
+
// rotate offset back to "camera-up-vector-is-up" space
|
389 |
+
_v.applyQuaternion( this._quatInverse );
|
390 |
+
|
391 |
+
position.copy( this.target ).add( _v );
|
392 |
+
|
393 |
+
this.object.lookAt( this.target );
|
394 |
+
|
395 |
+
if ( this.enableDamping === true ) {
|
396 |
+
|
397 |
+
this._sphericalDelta.theta *= ( 1 - this.dampingFactor );
|
398 |
+
this._sphericalDelta.phi *= ( 1 - this.dampingFactor );
|
399 |
+
|
400 |
+
this._panOffset.multiplyScalar( 1 - this.dampingFactor );
|
401 |
+
|
402 |
+
} else {
|
403 |
+
|
404 |
+
this._sphericalDelta.set( 0, 0, 0 );
|
405 |
+
|
406 |
+
this._panOffset.set( 0, 0, 0 );
|
407 |
+
|
408 |
+
}
|
409 |
+
|
410 |
+
// adjust camera position
|
411 |
+
if ( this.zoomToCursor && this._performCursorZoom ) {
|
412 |
+
|
413 |
+
let newRadius = null;
|
414 |
+
if ( this.object.isPerspectiveCamera ) {
|
415 |
+
|
416 |
+
// move the camera down the pointer ray
|
417 |
+
// this method avoids floating point error
|
418 |
+
const prevRadius = _v.length();
|
419 |
+
newRadius = this._clampDistance( prevRadius * this._scale );
|
420 |
+
|
421 |
+
const radiusDelta = prevRadius - newRadius;
|
422 |
+
this.object.position.addScaledVector( this._dollyDirection, radiusDelta );
|
423 |
+
this.object.updateMatrixWorld();
|
424 |
+
|
425 |
+
zoomChanged = !! radiusDelta;
|
426 |
+
|
427 |
+
} else if ( this.object.isOrthographicCamera ) {
|
428 |
+
|
429 |
+
// adjust the ortho camera position based on zoom changes
|
430 |
+
const mouseBefore = new Vector3( this._mouse.x, this._mouse.y, 0 );
|
431 |
+
mouseBefore.unproject( this.object );
|
432 |
+
|
433 |
+
const prevZoom = this.object.zoom;
|
434 |
+
this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
|
435 |
+
this.object.updateProjectionMatrix();
|
436 |
+
|
437 |
+
zoomChanged = prevZoom !== this.object.zoom;
|
438 |
+
|
439 |
+
const mouseAfter = new Vector3( this._mouse.x, this._mouse.y, 0 );
|
440 |
+
mouseAfter.unproject( this.object );
|
441 |
+
|
442 |
+
this.object.position.sub( mouseAfter ).add( mouseBefore );
|
443 |
+
this.object.updateMatrixWorld();
|
444 |
+
|
445 |
+
newRadius = _v.length();
|
446 |
+
|
447 |
+
} else {
|
448 |
+
|
449 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - zoom to cursor disabled.' );
|
450 |
+
this.zoomToCursor = false;
|
451 |
+
|
452 |
+
}
|
453 |
+
|
454 |
+
// handle the placement of the target
|
455 |
+
if ( newRadius !== null ) {
|
456 |
+
|
457 |
+
if ( this.screenSpacePanning ) {
|
458 |
+
|
459 |
+
// position the orbit target in front of the new camera position
|
460 |
+
this.target.set( 0, 0, - 1 )
|
461 |
+
.transformDirection( this.object.matrix )
|
462 |
+
.multiplyScalar( newRadius )
|
463 |
+
.add( this.object.position );
|
464 |
+
|
465 |
+
} else {
|
466 |
+
|
467 |
+
// get the ray and translation plane to compute target
|
468 |
+
_ray.origin.copy( this.object.position );
|
469 |
+
_ray.direction.set( 0, 0, - 1 ).transformDirection( this.object.matrix );
|
470 |
+
|
471 |
+
// if the camera is 20 degrees above the horizon then don't adjust the focus target to avoid
|
472 |
+
// extremely large values
|
473 |
+
if ( Math.abs( this.object.up.dot( _ray.direction ) ) < _TILT_LIMIT ) {
|
474 |
+
|
475 |
+
this.object.lookAt( this.target );
|
476 |
+
|
477 |
+
} else {
|
478 |
+
|
479 |
+
_plane.setFromNormalAndCoplanarPoint( this.object.up, this.target );
|
480 |
+
_ray.intersectPlane( _plane, this.target );
|
481 |
+
|
482 |
+
}
|
483 |
+
|
484 |
+
}
|
485 |
+
|
486 |
+
}
|
487 |
+
|
488 |
+
} else if ( this.object.isOrthographicCamera ) {
|
489 |
+
|
490 |
+
const prevZoom = this.object.zoom;
|
491 |
+
this.object.zoom = Math.max( this.minZoom, Math.min( this.maxZoom, this.object.zoom / this._scale ) );
|
492 |
+
|
493 |
+
if ( prevZoom !== this.object.zoom ) {
|
494 |
+
|
495 |
+
this.object.updateProjectionMatrix();
|
496 |
+
zoomChanged = true;
|
497 |
+
|
498 |
+
}
|
499 |
+
|
500 |
+
}
|
501 |
+
|
502 |
+
this._scale = 1;
|
503 |
+
this._performCursorZoom = false;
|
504 |
+
|
505 |
+
// update condition is:
|
506 |
+
// min(camera displacement, camera rotation in radians)^2 > EPS
|
507 |
+
// using small-angle approximation cos(x/2) = 1 - x^2 / 8
|
508 |
+
|
509 |
+
if ( zoomChanged ||
|
510 |
+
this._lastPosition.distanceToSquared( this.object.position ) > _EPS ||
|
511 |
+
8 * ( 1 - this._lastQuaternion.dot( this.object.quaternion ) ) > _EPS ||
|
512 |
+
this._lastTargetPosition.distanceToSquared( this.target ) > _EPS ) {
|
513 |
+
|
514 |
+
this.dispatchEvent( _changeEvent );
|
515 |
+
|
516 |
+
this._lastPosition.copy( this.object.position );
|
517 |
+
this._lastQuaternion.copy( this.object.quaternion );
|
518 |
+
this._lastTargetPosition.copy( this.target );
|
519 |
+
|
520 |
+
return true;
|
521 |
+
|
522 |
+
}
|
523 |
+
|
524 |
+
return false;
|
525 |
+
|
526 |
+
}
|
527 |
+
|
528 |
+
_getAutoRotationAngle( deltaTime ) {
|
529 |
+
|
530 |
+
if ( deltaTime !== null ) {
|
531 |
+
|
532 |
+
return ( _twoPI / 60 * this.autoRotateSpeed ) * deltaTime;
|
533 |
+
|
534 |
+
} else {
|
535 |
+
|
536 |
+
return _twoPI / 60 / 60 * this.autoRotateSpeed;
|
537 |
+
|
538 |
+
}
|
539 |
+
|
540 |
+
}
|
541 |
+
|
542 |
+
_getZoomScale( delta ) {
|
543 |
+
|
544 |
+
const normalizedDelta = Math.abs( delta * 0.01 );
|
545 |
+
return Math.pow( 0.95, this.zoomSpeed * normalizedDelta );
|
546 |
+
|
547 |
+
}
|
548 |
+
|
549 |
+
_rotateLeft( angle ) {
|
550 |
+
|
551 |
+
this._sphericalDelta.theta -= angle;
|
552 |
+
|
553 |
+
}
|
554 |
+
|
555 |
+
_rotateUp( angle ) {
|
556 |
+
|
557 |
+
this._sphericalDelta.phi -= angle;
|
558 |
+
|
559 |
+
}
|
560 |
+
|
561 |
+
_panLeft( distance, objectMatrix ) {
|
562 |
+
|
563 |
+
_v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
|
564 |
+
_v.multiplyScalar( - distance );
|
565 |
+
|
566 |
+
this._panOffset.add( _v );
|
567 |
+
|
568 |
+
}
|
569 |
+
|
570 |
+
_panUp( distance, objectMatrix ) {
|
571 |
+
|
572 |
+
if ( this.screenSpacePanning === true ) {
|
573 |
+
|
574 |
+
_v.setFromMatrixColumn( objectMatrix, 1 );
|
575 |
+
|
576 |
+
} else {
|
577 |
+
|
578 |
+
_v.setFromMatrixColumn( objectMatrix, 0 );
|
579 |
+
_v.crossVectors( this.object.up, _v );
|
580 |
+
|
581 |
+
}
|
582 |
+
|
583 |
+
_v.multiplyScalar( distance );
|
584 |
+
|
585 |
+
this._panOffset.add( _v );
|
586 |
+
|
587 |
+
}
|
588 |
+
|
589 |
+
// deltaX and deltaY are in pixels; right and down are positive
|
590 |
+
_pan( deltaX, deltaY ) {
|
591 |
+
|
592 |
+
const element = this.domElement;
|
593 |
+
|
594 |
+
if ( this.object.isPerspectiveCamera ) {
|
595 |
+
|
596 |
+
// perspective
|
597 |
+
const position = this.object.position;
|
598 |
+
_v.copy( position ).sub( this.target );
|
599 |
+
let targetDistance = _v.length();
|
600 |
+
|
601 |
+
// half of the fov is center to top of screen
|
602 |
+
targetDistance *= Math.tan( ( this.object.fov / 2 ) * Math.PI / 180.0 );
|
603 |
+
|
604 |
+
// we use only clientHeight here so aspect ratio does not distort speed
|
605 |
+
this._panLeft( 2 * deltaX * targetDistance / element.clientHeight, this.object.matrix );
|
606 |
+
this._panUp( 2 * deltaY * targetDistance / element.clientHeight, this.object.matrix );
|
607 |
+
|
608 |
+
} else if ( this.object.isOrthographicCamera ) {
|
609 |
+
|
610 |
+
// orthographic
|
611 |
+
this._panLeft( deltaX * ( this.object.right - this.object.left ) / this.object.zoom / element.clientWidth, this.object.matrix );
|
612 |
+
this._panUp( deltaY * ( this.object.top - this.object.bottom ) / this.object.zoom / element.clientHeight, this.object.matrix );
|
613 |
+
|
614 |
+
} else {
|
615 |
+
|
616 |
+
// camera neither orthographic nor perspective
|
617 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
|
618 |
+
this.enablePan = false;
|
619 |
+
|
620 |
+
}
|
621 |
+
|
622 |
+
}
|
623 |
+
|
624 |
+
_dollyOut( dollyScale ) {
|
625 |
+
|
626 |
+
if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {
|
627 |
+
|
628 |
+
this._scale /= dollyScale;
|
629 |
+
|
630 |
+
} else {
|
631 |
+
|
632 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
633 |
+
this.enableZoom = false;
|
634 |
+
|
635 |
+
}
|
636 |
+
|
637 |
+
}
|
638 |
+
|
639 |
+
_dollyIn( dollyScale ) {
|
640 |
+
|
641 |
+
if ( this.object.isPerspectiveCamera || this.object.isOrthographicCamera ) {
|
642 |
+
|
643 |
+
this._scale *= dollyScale;
|
644 |
+
|
645 |
+
} else {
|
646 |
+
|
647 |
+
console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
|
648 |
+
this.enableZoom = false;
|
649 |
+
|
650 |
+
}
|
651 |
+
|
652 |
+
}
|
653 |
+
|
654 |
+
_updateZoomParameters( x, y ) {
|
655 |
+
|
656 |
+
if ( ! this.zoomToCursor ) {
|
657 |
+
|
658 |
+
return;
|
659 |
+
|
660 |
+
}
|
661 |
+
|
662 |
+
this._performCursorZoom = true;
|
663 |
+
|
664 |
+
const rect = this.domElement.getBoundingClientRect();
|
665 |
+
const dx = x - rect.left;
|
666 |
+
const dy = y - rect.top;
|
667 |
+
const w = rect.width;
|
668 |
+
const h = rect.height;
|
669 |
+
|
670 |
+
this._mouse.x = ( dx / w ) * 2 - 1;
|
671 |
+
this._mouse.y = - ( dy / h ) * 2 + 1;
|
672 |
+
|
673 |
+
this._dollyDirection.set( this._mouse.x, this._mouse.y, 1 ).unproject( this.object ).sub( this.object.position ).normalize();
|
674 |
+
|
675 |
+
}
|
676 |
+
|
677 |
+
_clampDistance( dist ) {
|
678 |
+
|
679 |
+
return Math.max( this.minDistance, Math.min( this.maxDistance, dist ) );
|
680 |
+
|
681 |
+
}
|
682 |
+
|
683 |
+
//
|
684 |
+
// event callbacks - update the object state
|
685 |
+
//
|
686 |
+
|
687 |
+
_handleMouseDownRotate( event ) {
|
688 |
+
|
689 |
+
this._rotateStart.set( event.clientX, event.clientY );
|
690 |
+
|
691 |
+
}
|
692 |
+
|
693 |
+
_handleMouseDownDolly( event ) {
|
694 |
+
|
695 |
+
this._updateZoomParameters( event.clientX, event.clientX );
|
696 |
+
this._dollyStart.set( event.clientX, event.clientY );
|
697 |
+
|
698 |
+
}
|
699 |
+
|
700 |
+
_handleMouseDownPan( event ) {
|
701 |
+
|
702 |
+
this._panStart.set( event.clientX, event.clientY );
|
703 |
+
|
704 |
+
}
|
705 |
+
|
706 |
+
_handleMouseMoveRotate( event ) {
|
707 |
+
|
708 |
+
this._rotateEnd.set( event.clientX, event.clientY );
|
709 |
+
|
710 |
+
this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );
|
711 |
+
|
712 |
+
const element = this.domElement;
|
713 |
+
|
714 |
+
this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height
|
715 |
+
|
716 |
+
this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );
|
717 |
+
|
718 |
+
this._rotateStart.copy( this._rotateEnd );
|
719 |
+
|
720 |
+
this.update();
|
721 |
+
|
722 |
+
}
|
723 |
+
|
724 |
+
_handleMouseMoveDolly( event ) {
|
725 |
+
|
726 |
+
this._dollyEnd.set( event.clientX, event.clientY );
|
727 |
+
|
728 |
+
this._dollyDelta.subVectors( this._dollyEnd, this._dollyStart );
|
729 |
+
|
730 |
+
if ( this._dollyDelta.y > 0 ) {
|
731 |
+
|
732 |
+
this._dollyOut( this._getZoomScale( this._dollyDelta.y ) );
|
733 |
+
|
734 |
+
} else if ( this._dollyDelta.y < 0 ) {
|
735 |
+
|
736 |
+
this._dollyIn( this._getZoomScale( this._dollyDelta.y ) );
|
737 |
+
|
738 |
+
}
|
739 |
+
|
740 |
+
this._dollyStart.copy( this._dollyEnd );
|
741 |
+
|
742 |
+
this.update();
|
743 |
+
|
744 |
+
}
|
745 |
+
|
746 |
+
_handleMouseMovePan( event ) {
|
747 |
+
|
748 |
+
this._panEnd.set( event.clientX, event.clientY );
|
749 |
+
|
750 |
+
this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );
|
751 |
+
|
752 |
+
this._pan( this._panDelta.x, this._panDelta.y );
|
753 |
+
|
754 |
+
this._panStart.copy( this._panEnd );
|
755 |
+
|
756 |
+
this.update();
|
757 |
+
|
758 |
+
}
|
759 |
+
|
760 |
+
_handleMouseWheel( event ) {
|
761 |
+
|
762 |
+
this._updateZoomParameters( event.clientX, event.clientY );
|
763 |
+
|
764 |
+
if ( event.deltaY < 0 ) {
|
765 |
+
|
766 |
+
this._dollyIn( this._getZoomScale( event.deltaY ) );
|
767 |
+
|
768 |
+
} else if ( event.deltaY > 0 ) {
|
769 |
+
|
770 |
+
this._dollyOut( this._getZoomScale( event.deltaY ) );
|
771 |
+
|
772 |
+
}
|
773 |
+
|
774 |
+
this.update();
|
775 |
+
|
776 |
+
}
|
777 |
+
|
778 |
+
_handleKeyDown( event ) {
|
779 |
+
|
780 |
+
let needsUpdate = false;
|
781 |
+
|
782 |
+
switch ( event.code ) {
|
783 |
+
|
784 |
+
case this.keys.UP:
|
785 |
+
|
786 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
787 |
+
|
788 |
+
if ( this.enableRotate ) {
|
789 |
+
|
790 |
+
this._rotateUp( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );
|
791 |
+
|
792 |
+
}
|
793 |
+
|
794 |
+
} else {
|
795 |
+
|
796 |
+
if ( this.enablePan ) {
|
797 |
+
|
798 |
+
this._pan( 0, this.keyPanSpeed );
|
799 |
+
|
800 |
+
}
|
801 |
+
|
802 |
+
}
|
803 |
+
|
804 |
+
needsUpdate = true;
|
805 |
+
break;
|
806 |
+
|
807 |
+
case this.keys.BOTTOM:
|
808 |
+
|
809 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
810 |
+
|
811 |
+
if ( this.enableRotate ) {
|
812 |
+
|
813 |
+
this._rotateUp( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );
|
814 |
+
|
815 |
+
}
|
816 |
+
|
817 |
+
} else {
|
818 |
+
|
819 |
+
if ( this.enablePan ) {
|
820 |
+
|
821 |
+
this._pan( 0, - this.keyPanSpeed );
|
822 |
+
|
823 |
+
}
|
824 |
+
|
825 |
+
}
|
826 |
+
|
827 |
+
needsUpdate = true;
|
828 |
+
break;
|
829 |
+
|
830 |
+
case this.keys.LEFT:
|
831 |
+
|
832 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
833 |
+
|
834 |
+
if ( this.enableRotate ) {
|
835 |
+
|
836 |
+
this._rotateLeft( _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );
|
837 |
+
|
838 |
+
}
|
839 |
+
|
840 |
+
} else {
|
841 |
+
|
842 |
+
if ( this.enablePan ) {
|
843 |
+
|
844 |
+
this._pan( this.keyPanSpeed, 0 );
|
845 |
+
|
846 |
+
}
|
847 |
+
|
848 |
+
}
|
849 |
+
|
850 |
+
needsUpdate = true;
|
851 |
+
break;
|
852 |
+
|
853 |
+
case this.keys.RIGHT:
|
854 |
+
|
855 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
856 |
+
|
857 |
+
if ( this.enableRotate ) {
|
858 |
+
|
859 |
+
this._rotateLeft( - _twoPI * this.keyRotateSpeed / this.domElement.clientHeight );
|
860 |
+
|
861 |
+
}
|
862 |
+
|
863 |
+
} else {
|
864 |
+
|
865 |
+
if ( this.enablePan ) {
|
866 |
+
|
867 |
+
this._pan( - this.keyPanSpeed, 0 );
|
868 |
+
|
869 |
+
}
|
870 |
+
|
871 |
+
}
|
872 |
+
|
873 |
+
needsUpdate = true;
|
874 |
+
break;
|
875 |
+
|
876 |
+
}
|
877 |
+
|
878 |
+
if ( needsUpdate ) {
|
879 |
+
|
880 |
+
// prevent the browser from scrolling on cursor keys
|
881 |
+
event.preventDefault();
|
882 |
+
|
883 |
+
this.update();
|
884 |
+
|
885 |
+
}
|
886 |
+
|
887 |
+
|
888 |
+
}
|
889 |
+
|
890 |
+
_handleTouchStartRotate( event ) {
|
891 |
+
|
892 |
+
if ( this._pointers.length === 1 ) {
|
893 |
+
|
894 |
+
this._rotateStart.set( event.pageX, event.pageY );
|
895 |
+
|
896 |
+
} else {
|
897 |
+
|
898 |
+
const position = this._getSecondPointerPosition( event );
|
899 |
+
|
900 |
+
const x = 0.5 * ( event.pageX + position.x );
|
901 |
+
const y = 0.5 * ( event.pageY + position.y );
|
902 |
+
|
903 |
+
this._rotateStart.set( x, y );
|
904 |
+
|
905 |
+
}
|
906 |
+
|
907 |
+
}
|
908 |
+
|
909 |
+
_handleTouchStartPan( event ) {
|
910 |
+
|
911 |
+
if ( this._pointers.length === 1 ) {
|
912 |
+
|
913 |
+
this._panStart.set( event.pageX, event.pageY );
|
914 |
+
|
915 |
+
} else {
|
916 |
+
|
917 |
+
const position = this._getSecondPointerPosition( event );
|
918 |
+
|
919 |
+
const x = 0.5 * ( event.pageX + position.x );
|
920 |
+
const y = 0.5 * ( event.pageY + position.y );
|
921 |
+
|
922 |
+
this._panStart.set( x, y );
|
923 |
+
|
924 |
+
}
|
925 |
+
|
926 |
+
}
|
927 |
+
|
928 |
+
_handleTouchStartDolly( event ) {
|
929 |
+
|
930 |
+
const position = this._getSecondPointerPosition( event );
|
931 |
+
|
932 |
+
const dx = event.pageX - position.x;
|
933 |
+
const dy = event.pageY - position.y;
|
934 |
+
|
935 |
+
const distance = Math.sqrt( dx * dx + dy * dy );
|
936 |
+
|
937 |
+
this._dollyStart.set( 0, distance );
|
938 |
+
|
939 |
+
}
|
940 |
+
|
941 |
+
_handleTouchStartDollyPan( event ) {
|
942 |
+
|
943 |
+
if ( this.enableZoom ) this._handleTouchStartDolly( event );
|
944 |
+
|
945 |
+
if ( this.enablePan ) this._handleTouchStartPan( event );
|
946 |
+
|
947 |
+
}
|
948 |
+
|
949 |
+
_handleTouchStartDollyRotate( event ) {
|
950 |
+
|
951 |
+
if ( this.enableZoom ) this._handleTouchStartDolly( event );
|
952 |
+
|
953 |
+
if ( this.enableRotate ) this._handleTouchStartRotate( event );
|
954 |
+
|
955 |
+
}
|
956 |
+
|
957 |
+
_handleTouchMoveRotate( event ) {
|
958 |
+
|
959 |
+
if ( this._pointers.length == 1 ) {
|
960 |
+
|
961 |
+
this._rotateEnd.set( event.pageX, event.pageY );
|
962 |
+
|
963 |
+
} else {
|
964 |
+
|
965 |
+
const position = this._getSecondPointerPosition( event );
|
966 |
+
|
967 |
+
const x = 0.5 * ( event.pageX + position.x );
|
968 |
+
const y = 0.5 * ( event.pageY + position.y );
|
969 |
+
|
970 |
+
this._rotateEnd.set( x, y );
|
971 |
+
|
972 |
+
}
|
973 |
+
|
974 |
+
this._rotateDelta.subVectors( this._rotateEnd, this._rotateStart ).multiplyScalar( this.rotateSpeed );
|
975 |
+
|
976 |
+
const element = this.domElement;
|
977 |
+
|
978 |
+
this._rotateLeft( _twoPI * this._rotateDelta.x / element.clientHeight ); // yes, height
|
979 |
+
|
980 |
+
this._rotateUp( _twoPI * this._rotateDelta.y / element.clientHeight );
|
981 |
+
|
982 |
+
this._rotateStart.copy( this._rotateEnd );
|
983 |
+
|
984 |
+
}
|
985 |
+
|
986 |
+
_handleTouchMovePan( event ) {
|
987 |
+
|
988 |
+
if ( this._pointers.length === 1 ) {
|
989 |
+
|
990 |
+
this._panEnd.set( event.pageX, event.pageY );
|
991 |
+
|
992 |
+
} else {
|
993 |
+
|
994 |
+
const position = this._getSecondPointerPosition( event );
|
995 |
+
|
996 |
+
const x = 0.5 * ( event.pageX + position.x );
|
997 |
+
const y = 0.5 * ( event.pageY + position.y );
|
998 |
+
|
999 |
+
this._panEnd.set( x, y );
|
1000 |
+
|
1001 |
+
}
|
1002 |
+
|
1003 |
+
this._panDelta.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.panSpeed );
|
1004 |
+
|
1005 |
+
this._pan( this._panDelta.x, this._panDelta.y );
|
1006 |
+
|
1007 |
+
this._panStart.copy( this._panEnd );
|
1008 |
+
|
1009 |
+
}
|
1010 |
+
|
1011 |
+
_handleTouchMoveDolly( event ) {
|
1012 |
+
|
1013 |
+
const position = this._getSecondPointerPosition( event );
|
1014 |
+
|
1015 |
+
const dx = event.pageX - position.x;
|
1016 |
+
const dy = event.pageY - position.y;
|
1017 |
+
|
1018 |
+
const distance = Math.sqrt( dx * dx + dy * dy );
|
1019 |
+
|
1020 |
+
this._dollyEnd.set( 0, distance );
|
1021 |
+
|
1022 |
+
this._dollyDelta.set( 0, Math.pow( this._dollyEnd.y / this._dollyStart.y, this.zoomSpeed ) );
|
1023 |
+
|
1024 |
+
this._dollyOut( this._dollyDelta.y );
|
1025 |
+
|
1026 |
+
this._dollyStart.copy( this._dollyEnd );
|
1027 |
+
|
1028 |
+
const centerX = ( event.pageX + position.x ) * 0.5;
|
1029 |
+
const centerY = ( event.pageY + position.y ) * 0.5;
|
1030 |
+
|
1031 |
+
this._updateZoomParameters( centerX, centerY );
|
1032 |
+
|
1033 |
+
}
|
1034 |
+
|
1035 |
+
_handleTouchMoveDollyPan( event ) {
|
1036 |
+
|
1037 |
+
if ( this.enableZoom ) this._handleTouchMoveDolly( event );
|
1038 |
+
|
1039 |
+
if ( this.enablePan ) this._handleTouchMovePan( event );
|
1040 |
+
|
1041 |
+
}
|
1042 |
+
|
1043 |
+
_handleTouchMoveDollyRotate( event ) {
|
1044 |
+
|
1045 |
+
if ( this.enableZoom ) this._handleTouchMoveDolly( event );
|
1046 |
+
|
1047 |
+
if ( this.enableRotate ) this._handleTouchMoveRotate( event );
|
1048 |
+
|
1049 |
+
}
|
1050 |
+
|
1051 |
+
// pointers
|
1052 |
+
|
1053 |
+
_addPointer( event ) {
|
1054 |
+
|
1055 |
+
this._pointers.push( event.pointerId );
|
1056 |
+
|
1057 |
+
}
|
1058 |
+
|
1059 |
+
_removePointer( event ) {
|
1060 |
+
|
1061 |
+
delete this._pointerPositions[ event.pointerId ];
|
1062 |
+
|
1063 |
+
for ( let i = 0; i < this._pointers.length; i ++ ) {
|
1064 |
+
|
1065 |
+
if ( this._pointers[ i ] == event.pointerId ) {
|
1066 |
+
|
1067 |
+
this._pointers.splice( i, 1 );
|
1068 |
+
return;
|
1069 |
+
|
1070 |
+
}
|
1071 |
+
|
1072 |
+
}
|
1073 |
+
|
1074 |
+
}
|
1075 |
+
|
1076 |
+
_isTrackingPointer( event ) {
|
1077 |
+
|
1078 |
+
for ( let i = 0; i < this._pointers.length; i ++ ) {
|
1079 |
+
|
1080 |
+
if ( this._pointers[ i ] == event.pointerId ) return true;
|
1081 |
+
|
1082 |
+
}
|
1083 |
+
|
1084 |
+
return false;
|
1085 |
+
|
1086 |
+
}
|
1087 |
+
|
1088 |
+
_trackPointer( event ) {
|
1089 |
+
|
1090 |
+
let position = this._pointerPositions[ event.pointerId ];
|
1091 |
+
|
1092 |
+
if ( position === undefined ) {
|
1093 |
+
|
1094 |
+
position = new Vector2();
|
1095 |
+
this._pointerPositions[ event.pointerId ] = position;
|
1096 |
+
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
position.set( event.pageX, event.pageY );
|
1100 |
+
|
1101 |
+
}
|
1102 |
+
|
1103 |
+
_getSecondPointerPosition( event ) {
|
1104 |
+
|
1105 |
+
const pointerId = ( event.pointerId === this._pointers[ 0 ] ) ? this._pointers[ 1 ] : this._pointers[ 0 ];
|
1106 |
+
|
1107 |
+
return this._pointerPositions[ pointerId ];
|
1108 |
+
|
1109 |
+
}
|
1110 |
+
|
1111 |
+
//
|
1112 |
+
|
1113 |
+
_customWheelEvent( event ) {
|
1114 |
+
|
1115 |
+
const mode = event.deltaMode;
|
1116 |
+
|
1117 |
+
// minimal wheel event altered to meet delta-zoom demand
|
1118 |
+
const newEvent = {
|
1119 |
+
clientX: event.clientX,
|
1120 |
+
clientY: event.clientY,
|
1121 |
+
deltaY: event.deltaY,
|
1122 |
+
};
|
1123 |
+
|
1124 |
+
switch ( mode ) {
|
1125 |
+
|
1126 |
+
case 1: // LINE_MODE
|
1127 |
+
newEvent.deltaY *= 16;
|
1128 |
+
break;
|
1129 |
+
|
1130 |
+
case 2: // PAGE_MODE
|
1131 |
+
newEvent.deltaY *= 100;
|
1132 |
+
break;
|
1133 |
+
|
1134 |
+
}
|
1135 |
+
|
1136 |
+
// detect if event was triggered by pinching
|
1137 |
+
if ( event.ctrlKey && ! this._controlActive ) {
|
1138 |
+
|
1139 |
+
newEvent.deltaY *= 10;
|
1140 |
+
|
1141 |
+
}
|
1142 |
+
|
1143 |
+
return newEvent;
|
1144 |
+
|
1145 |
+
}
|
1146 |
+
|
1147 |
+
}
|
1148 |
+
|
1149 |
+
function onPointerDown( event ) {
|
1150 |
+
|
1151 |
+
if ( this.enabled === false ) return;
|
1152 |
+
|
1153 |
+
if ( this._pointers.length === 0 ) {
|
1154 |
+
|
1155 |
+
this.domElement.setPointerCapture( event.pointerId );
|
1156 |
+
|
1157 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
1158 |
+
this.domElement.addEventListener( 'pointerup', this._onPointerUp );
|
1159 |
+
|
1160 |
+
}
|
1161 |
+
|
1162 |
+
//
|
1163 |
+
|
1164 |
+
if ( this._isTrackingPointer( event ) ) return;
|
1165 |
+
|
1166 |
+
//
|
1167 |
+
|
1168 |
+
this._addPointer( event );
|
1169 |
+
|
1170 |
+
if ( event.pointerType === 'touch' ) {
|
1171 |
+
|
1172 |
+
this._onTouchStart( event );
|
1173 |
+
|
1174 |
+
} else {
|
1175 |
+
|
1176 |
+
this._onMouseDown( event );
|
1177 |
+
|
1178 |
+
}
|
1179 |
+
|
1180 |
+
}
|
1181 |
+
|
1182 |
+
function onPointerMove( event ) {
|
1183 |
+
|
1184 |
+
if ( this.enabled === false ) return;
|
1185 |
+
|
1186 |
+
if ( event.pointerType === 'touch' ) {
|
1187 |
+
|
1188 |
+
this._onTouchMove( event );
|
1189 |
+
|
1190 |
+
} else {
|
1191 |
+
|
1192 |
+
this._onMouseMove( event );
|
1193 |
+
|
1194 |
+
}
|
1195 |
+
|
1196 |
+
}
|
1197 |
+
|
1198 |
+
function onPointerUp( event ) {
|
1199 |
+
|
1200 |
+
this._removePointer( event );
|
1201 |
+
|
1202 |
+
switch ( this._pointers.length ) {
|
1203 |
+
|
1204 |
+
case 0:
|
1205 |
+
|
1206 |
+
this.domElement.releasePointerCapture( event.pointerId );
|
1207 |
+
|
1208 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
1209 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
1210 |
+
|
1211 |
+
this.dispatchEvent( _endEvent );
|
1212 |
+
|
1213 |
+
this.state = _STATE.NONE;
|
1214 |
+
|
1215 |
+
break;
|
1216 |
+
|
1217 |
+
case 1:
|
1218 |
+
|
1219 |
+
const pointerId = this._pointers[ 0 ];
|
1220 |
+
const position = this._pointerPositions[ pointerId ];
|
1221 |
+
|
1222 |
+
// minimal placeholder event - allows state correction on pointer-up
|
1223 |
+
this._onTouchStart( { pointerId: pointerId, pageX: position.x, pageY: position.y } );
|
1224 |
+
|
1225 |
+
break;
|
1226 |
+
|
1227 |
+
}
|
1228 |
+
|
1229 |
+
}
|
1230 |
+
|
1231 |
+
function onMouseDown( event ) {
|
1232 |
+
|
1233 |
+
let mouseAction;
|
1234 |
+
|
1235 |
+
switch ( event.button ) {
|
1236 |
+
|
1237 |
+
case 0:
|
1238 |
+
|
1239 |
+
mouseAction = this.mouseButtons.LEFT;
|
1240 |
+
break;
|
1241 |
+
|
1242 |
+
case 1:
|
1243 |
+
|
1244 |
+
mouseAction = this.mouseButtons.MIDDLE;
|
1245 |
+
break;
|
1246 |
+
|
1247 |
+
case 2:
|
1248 |
+
|
1249 |
+
mouseAction = this.mouseButtons.RIGHT;
|
1250 |
+
break;
|
1251 |
+
|
1252 |
+
default:
|
1253 |
+
|
1254 |
+
mouseAction = - 1;
|
1255 |
+
|
1256 |
+
}
|
1257 |
+
|
1258 |
+
switch ( mouseAction ) {
|
1259 |
+
|
1260 |
+
case MOUSE.DOLLY:
|
1261 |
+
|
1262 |
+
if ( this.enableZoom === false ) return;
|
1263 |
+
|
1264 |
+
this._handleMouseDownDolly( event );
|
1265 |
+
|
1266 |
+
this.state = _STATE.DOLLY;
|
1267 |
+
|
1268 |
+
break;
|
1269 |
+
|
1270 |
+
case MOUSE.ROTATE:
|
1271 |
+
|
1272 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
1273 |
+
|
1274 |
+
if ( this.enablePan === false ) return;
|
1275 |
+
|
1276 |
+
this._handleMouseDownPan( event );
|
1277 |
+
|
1278 |
+
this.state = _STATE.PAN;
|
1279 |
+
|
1280 |
+
} else {
|
1281 |
+
|
1282 |
+
if ( this.enableRotate === false ) return;
|
1283 |
+
|
1284 |
+
this._handleMouseDownRotate( event );
|
1285 |
+
|
1286 |
+
this.state = _STATE.ROTATE;
|
1287 |
+
|
1288 |
+
}
|
1289 |
+
|
1290 |
+
break;
|
1291 |
+
|
1292 |
+
case MOUSE.PAN:
|
1293 |
+
|
1294 |
+
if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
|
1295 |
+
|
1296 |
+
if ( this.enableRotate === false ) return;
|
1297 |
+
|
1298 |
+
this._handleMouseDownRotate( event );
|
1299 |
+
|
1300 |
+
this.state = _STATE.ROTATE;
|
1301 |
+
|
1302 |
+
} else {
|
1303 |
+
|
1304 |
+
if ( this.enablePan === false ) return;
|
1305 |
+
|
1306 |
+
this._handleMouseDownPan( event );
|
1307 |
+
|
1308 |
+
this.state = _STATE.PAN;
|
1309 |
+
|
1310 |
+
}
|
1311 |
+
|
1312 |
+
break;
|
1313 |
+
|
1314 |
+
default:
|
1315 |
+
|
1316 |
+
this.state = _STATE.NONE;
|
1317 |
+
|
1318 |
+
}
|
1319 |
+
|
1320 |
+
if ( this.state !== _STATE.NONE ) {
|
1321 |
+
|
1322 |
+
this.dispatchEvent( _startEvent );
|
1323 |
+
|
1324 |
+
}
|
1325 |
+
|
1326 |
+
}
|
1327 |
+
|
1328 |
+
function onMouseMove( event ) {
|
1329 |
+
|
1330 |
+
switch ( this.state ) {
|
1331 |
+
|
1332 |
+
case _STATE.ROTATE:
|
1333 |
+
|
1334 |
+
if ( this.enableRotate === false ) return;
|
1335 |
+
|
1336 |
+
this._handleMouseMoveRotate( event );
|
1337 |
+
|
1338 |
+
break;
|
1339 |
+
|
1340 |
+
case _STATE.DOLLY:
|
1341 |
+
|
1342 |
+
if ( this.enableZoom === false ) return;
|
1343 |
+
|
1344 |
+
this._handleMouseMoveDolly( event );
|
1345 |
+
|
1346 |
+
break;
|
1347 |
+
|
1348 |
+
case _STATE.PAN:
|
1349 |
+
|
1350 |
+
if ( this.enablePan === false ) return;
|
1351 |
+
|
1352 |
+
this._handleMouseMovePan( event );
|
1353 |
+
|
1354 |
+
break;
|
1355 |
+
|
1356 |
+
}
|
1357 |
+
|
1358 |
+
}
|
1359 |
+
|
1360 |
+
function onMouseWheel( event ) {
|
1361 |
+
|
1362 |
+
if ( this.enabled === false || this.enableZoom === false || this.state !== _STATE.NONE ) return;
|
1363 |
+
|
1364 |
+
event.preventDefault();
|
1365 |
+
|
1366 |
+
this.dispatchEvent( _startEvent );
|
1367 |
+
|
1368 |
+
this._handleMouseWheel( this._customWheelEvent( event ) );
|
1369 |
+
|
1370 |
+
this.dispatchEvent( _endEvent );
|
1371 |
+
|
1372 |
+
}
|
1373 |
+
|
1374 |
+
function onKeyDown( event ) {
|
1375 |
+
|
1376 |
+
if ( this.enabled === false ) return;
|
1377 |
+
|
1378 |
+
this._handleKeyDown( event );
|
1379 |
+
|
1380 |
+
}
|
1381 |
+
|
1382 |
+
function onTouchStart( event ) {
|
1383 |
+
|
1384 |
+
this._trackPointer( event );
|
1385 |
+
|
1386 |
+
switch ( this._pointers.length ) {
|
1387 |
+
|
1388 |
+
case 1:
|
1389 |
+
|
1390 |
+
switch ( this.touches.ONE ) {
|
1391 |
+
|
1392 |
+
case TOUCH.ROTATE:
|
1393 |
+
|
1394 |
+
if ( this.enableRotate === false ) return;
|
1395 |
+
|
1396 |
+
this._handleTouchStartRotate( event );
|
1397 |
+
|
1398 |
+
this.state = _STATE.TOUCH_ROTATE;
|
1399 |
+
|
1400 |
+
break;
|
1401 |
+
|
1402 |
+
case TOUCH.PAN:
|
1403 |
+
|
1404 |
+
if ( this.enablePan === false ) return;
|
1405 |
+
|
1406 |
+
this._handleTouchStartPan( event );
|
1407 |
+
|
1408 |
+
this.state = _STATE.TOUCH_PAN;
|
1409 |
+
|
1410 |
+
break;
|
1411 |
+
|
1412 |
+
default:
|
1413 |
+
|
1414 |
+
this.state = _STATE.NONE;
|
1415 |
+
|
1416 |
+
}
|
1417 |
+
|
1418 |
+
break;
|
1419 |
+
|
1420 |
+
case 2:
|
1421 |
+
|
1422 |
+
switch ( this.touches.TWO ) {
|
1423 |
+
|
1424 |
+
case TOUCH.DOLLY_PAN:
|
1425 |
+
|
1426 |
+
if ( this.enableZoom === false && this.enablePan === false ) return;
|
1427 |
+
|
1428 |
+
this._handleTouchStartDollyPan( event );
|
1429 |
+
|
1430 |
+
this.state = _STATE.TOUCH_DOLLY_PAN;
|
1431 |
+
|
1432 |
+
break;
|
1433 |
+
|
1434 |
+
case TOUCH.DOLLY_ROTATE:
|
1435 |
+
|
1436 |
+
if ( this.enableZoom === false && this.enableRotate === false ) return;
|
1437 |
+
|
1438 |
+
this._handleTouchStartDollyRotate( event );
|
1439 |
+
|
1440 |
+
this.state = _STATE.TOUCH_DOLLY_ROTATE;
|
1441 |
+
|
1442 |
+
break;
|
1443 |
+
|
1444 |
+
default:
|
1445 |
+
|
1446 |
+
this.state = _STATE.NONE;
|
1447 |
+
|
1448 |
+
}
|
1449 |
+
|
1450 |
+
break;
|
1451 |
+
|
1452 |
+
default:
|
1453 |
+
|
1454 |
+
this.state = _STATE.NONE;
|
1455 |
+
|
1456 |
+
}
|
1457 |
+
|
1458 |
+
if ( this.state !== _STATE.NONE ) {
|
1459 |
+
|
1460 |
+
this.dispatchEvent( _startEvent );
|
1461 |
+
|
1462 |
+
}
|
1463 |
+
|
1464 |
+
}
|
1465 |
+
|
1466 |
+
function onTouchMove( event ) {
|
1467 |
+
|
1468 |
+
this._trackPointer( event );
|
1469 |
+
|
1470 |
+
switch ( this.state ) {
|
1471 |
+
|
1472 |
+
case _STATE.TOUCH_ROTATE:
|
1473 |
+
|
1474 |
+
if ( this.enableRotate === false ) return;
|
1475 |
+
|
1476 |
+
this._handleTouchMoveRotate( event );
|
1477 |
+
|
1478 |
+
this.update();
|
1479 |
+
|
1480 |
+
break;
|
1481 |
+
|
1482 |
+
case _STATE.TOUCH_PAN:
|
1483 |
+
|
1484 |
+
if ( this.enablePan === false ) return;
|
1485 |
+
|
1486 |
+
this._handleTouchMovePan( event );
|
1487 |
+
|
1488 |
+
this.update();
|
1489 |
+
|
1490 |
+
break;
|
1491 |
+
|
1492 |
+
case _STATE.TOUCH_DOLLY_PAN:
|
1493 |
+
|
1494 |
+
if ( this.enableZoom === false && this.enablePan === false ) return;
|
1495 |
+
|
1496 |
+
this._handleTouchMoveDollyPan( event );
|
1497 |
+
|
1498 |
+
this.update();
|
1499 |
+
|
1500 |
+
break;
|
1501 |
+
|
1502 |
+
case _STATE.TOUCH_DOLLY_ROTATE:
|
1503 |
+
|
1504 |
+
if ( this.enableZoom === false && this.enableRotate === false ) return;
|
1505 |
+
|
1506 |
+
this._handleTouchMoveDollyRotate( event );
|
1507 |
+
|
1508 |
+
this.update();
|
1509 |
+
|
1510 |
+
break;
|
1511 |
+
|
1512 |
+
default:
|
1513 |
+
|
1514 |
+
this.state = _STATE.NONE;
|
1515 |
+
|
1516 |
+
}
|
1517 |
+
|
1518 |
+
}
|
1519 |
+
|
1520 |
+
function onContextMenu( event ) {
|
1521 |
+
|
1522 |
+
if ( this.enabled === false ) return;
|
1523 |
+
|
1524 |
+
event.preventDefault();
|
1525 |
+
|
1526 |
+
}
|
1527 |
+
|
1528 |
+
function interceptControlDown( event ) {
|
1529 |
+
|
1530 |
+
if ( event.key === 'Control' ) {
|
1531 |
+
|
1532 |
+
this._controlActive = true;
|
1533 |
+
|
1534 |
+
const document = this.domElement.getRootNode(); // offscreen canvas compatibility
|
1535 |
+
|
1536 |
+
document.addEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } );
|
1537 |
+
|
1538 |
+
}
|
1539 |
+
|
1540 |
+
}
|
1541 |
+
|
1542 |
+
function interceptControlUp( event ) {
|
1543 |
+
|
1544 |
+
if ( event.key === 'Control' ) {
|
1545 |
+
|
1546 |
+
this._controlActive = false;
|
1547 |
+
|
1548 |
+
const document = this.domElement.getRootNode(); // offscreen canvas compatibility
|
1549 |
+
|
1550 |
+
document.removeEventListener( 'keyup', this._interceptControlUp, { passive: true, capture: true } );
|
1551 |
+
|
1552 |
+
}
|
1553 |
+
|
1554 |
+
}
|
1555 |
+
|
1556 |
+
export { OrbitControls };
|
libs/three.js/0.172.0/jsm/controls/PointerLockControls.js
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
Euler,
|
4 |
+
Vector3
|
5 |
+
} from 'three';
|
6 |
+
|
7 |
+
const _euler = new Euler( 0, 0, 0, 'YXZ' );
|
8 |
+
const _vector = new Vector3();
|
9 |
+
|
10 |
+
const _changeEvent = { type: 'change' };
|
11 |
+
const _lockEvent = { type: 'lock' };
|
12 |
+
const _unlockEvent = { type: 'unlock' };
|
13 |
+
|
14 |
+
const _PI_2 = Math.PI / 2;
|
15 |
+
|
16 |
+
class PointerLockControls extends Controls {
|
17 |
+
|
18 |
+
constructor( camera, domElement = null ) {
|
19 |
+
|
20 |
+
super( camera, domElement );
|
21 |
+
|
22 |
+
this.isLocked = false;
|
23 |
+
|
24 |
+
// Set to constrain the pitch of the camera
|
25 |
+
// Range is 0 to Math.PI radians
|
26 |
+
this.minPolarAngle = 0; // radians
|
27 |
+
this.maxPolarAngle = Math.PI; // radians
|
28 |
+
|
29 |
+
this.pointerSpeed = 1.0;
|
30 |
+
|
31 |
+
// event listeners
|
32 |
+
|
33 |
+
this._onMouseMove = onMouseMove.bind( this );
|
34 |
+
this._onPointerlockChange = onPointerlockChange.bind( this );
|
35 |
+
this._onPointerlockError = onPointerlockError.bind( this );
|
36 |
+
|
37 |
+
if ( this.domElement !== null ) {
|
38 |
+
|
39 |
+
this.connect();
|
40 |
+
|
41 |
+
}
|
42 |
+
|
43 |
+
}
|
44 |
+
|
45 |
+
connect() {
|
46 |
+
|
47 |
+
this.domElement.ownerDocument.addEventListener( 'mousemove', this._onMouseMove );
|
48 |
+
this.domElement.ownerDocument.addEventListener( 'pointerlockchange', this._onPointerlockChange );
|
49 |
+
this.domElement.ownerDocument.addEventListener( 'pointerlockerror', this._onPointerlockError );
|
50 |
+
|
51 |
+
}
|
52 |
+
|
53 |
+
disconnect() {
|
54 |
+
|
55 |
+
this.domElement.ownerDocument.removeEventListener( 'mousemove', this._onMouseMove );
|
56 |
+
this.domElement.ownerDocument.removeEventListener( 'pointerlockchange', this._onPointerlockChange );
|
57 |
+
this.domElement.ownerDocument.removeEventListener( 'pointerlockerror', this._onPointerlockError );
|
58 |
+
|
59 |
+
}
|
60 |
+
|
61 |
+
dispose() {
|
62 |
+
|
63 |
+
this.disconnect();
|
64 |
+
|
65 |
+
}
|
66 |
+
|
67 |
+
getObject() {
|
68 |
+
|
69 |
+
console.warn( 'THREE.PointerLockControls: getObject() has been deprecated. Use controls.object instead.' ); // @deprecated r169
|
70 |
+
|
71 |
+
return this.object;
|
72 |
+
|
73 |
+
}
|
74 |
+
|
75 |
+
getDirection( v ) {
|
76 |
+
|
77 |
+
return v.set( 0, 0, - 1 ).applyQuaternion( this.object.quaternion );
|
78 |
+
|
79 |
+
}
|
80 |
+
|
81 |
+
moveForward( distance ) {
|
82 |
+
|
83 |
+
if ( this.enabled === false ) return;
|
84 |
+
|
85 |
+
// move forward parallel to the xz-plane
|
86 |
+
// assumes camera.up is y-up
|
87 |
+
|
88 |
+
const camera = this.object;
|
89 |
+
|
90 |
+
_vector.setFromMatrixColumn( camera.matrix, 0 );
|
91 |
+
|
92 |
+
_vector.crossVectors( camera.up, _vector );
|
93 |
+
|
94 |
+
camera.position.addScaledVector( _vector, distance );
|
95 |
+
|
96 |
+
}
|
97 |
+
|
98 |
+
moveRight( distance ) {
|
99 |
+
|
100 |
+
if ( this.enabled === false ) return;
|
101 |
+
|
102 |
+
const camera = this.object;
|
103 |
+
|
104 |
+
_vector.setFromMatrixColumn( camera.matrix, 0 );
|
105 |
+
|
106 |
+
camera.position.addScaledVector( _vector, distance );
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
lock() {
|
111 |
+
|
112 |
+
this.domElement.requestPointerLock();
|
113 |
+
|
114 |
+
}
|
115 |
+
|
116 |
+
unlock() {
|
117 |
+
|
118 |
+
this.domElement.ownerDocument.exitPointerLock();
|
119 |
+
|
120 |
+
}
|
121 |
+
|
122 |
+
}
|
123 |
+
|
124 |
+
// event listeners
|
125 |
+
|
126 |
+
function onMouseMove( event ) {
|
127 |
+
|
128 |
+
if ( this.enabled === false || this.isLocked === false ) return;
|
129 |
+
|
130 |
+
const camera = this.object;
|
131 |
+
_euler.setFromQuaternion( camera.quaternion );
|
132 |
+
|
133 |
+
_euler.y -= event.movementX * 0.002 * this.pointerSpeed;
|
134 |
+
_euler.x -= event.movementY * 0.002 * this.pointerSpeed;
|
135 |
+
|
136 |
+
_euler.x = Math.max( _PI_2 - this.maxPolarAngle, Math.min( _PI_2 - this.minPolarAngle, _euler.x ) );
|
137 |
+
|
138 |
+
camera.quaternion.setFromEuler( _euler );
|
139 |
+
|
140 |
+
this.dispatchEvent( _changeEvent );
|
141 |
+
|
142 |
+
}
|
143 |
+
|
144 |
+
function onPointerlockChange() {
|
145 |
+
|
146 |
+
if ( this.domElement.ownerDocument.pointerLockElement === this.domElement ) {
|
147 |
+
|
148 |
+
this.dispatchEvent( _lockEvent );
|
149 |
+
|
150 |
+
this.isLocked = true;
|
151 |
+
|
152 |
+
} else {
|
153 |
+
|
154 |
+
this.dispatchEvent( _unlockEvent );
|
155 |
+
|
156 |
+
this.isLocked = false;
|
157 |
+
|
158 |
+
}
|
159 |
+
|
160 |
+
}
|
161 |
+
|
162 |
+
function onPointerlockError() {
|
163 |
+
|
164 |
+
console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
|
165 |
+
|
166 |
+
}
|
167 |
+
|
168 |
+
export { PointerLockControls };
|
libs/three.js/0.172.0/jsm/controls/TrackballControls.js
ADDED
@@ -0,0 +1,849 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Controls,
|
3 |
+
MathUtils,
|
4 |
+
MOUSE,
|
5 |
+
Quaternion,
|
6 |
+
Vector2,
|
7 |
+
Vector3
|
8 |
+
} from 'three';
|
9 |
+
|
10 |
+
const _changeEvent = { type: 'change' };
|
11 |
+
const _startEvent = { type: 'start' };
|
12 |
+
const _endEvent = { type: 'end' };
|
13 |
+
|
14 |
+
const _EPS = 0.000001;
|
15 |
+
const _STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
|
16 |
+
|
17 |
+
const _v2 = new Vector2();
|
18 |
+
const _mouseChange = new Vector2();
|
19 |
+
const _objectUp = new Vector3();
|
20 |
+
const _pan = new Vector3();
|
21 |
+
const _axis = new Vector3();
|
22 |
+
const _quaternion = new Quaternion();
|
23 |
+
const _eyeDirection = new Vector3();
|
24 |
+
const _objectUpDirection = new Vector3();
|
25 |
+
const _objectSidewaysDirection = new Vector3();
|
26 |
+
const _moveDirection = new Vector3();
|
27 |
+
|
28 |
+
class TrackballControls extends Controls {
|
29 |
+
|
30 |
+
constructor( object, domElement = null ) {
|
31 |
+
|
32 |
+
super( object, domElement );
|
33 |
+
|
34 |
+
// API
|
35 |
+
|
36 |
+
this.enabled = true;
|
37 |
+
|
38 |
+
this.screen = { left: 0, top: 0, width: 0, height: 0 };
|
39 |
+
|
40 |
+
this.rotateSpeed = 1.0;
|
41 |
+
this.zoomSpeed = 1.2;
|
42 |
+
this.panSpeed = 0.3;
|
43 |
+
|
44 |
+
this.noRotate = false;
|
45 |
+
this.noZoom = false;
|
46 |
+
this.noPan = false;
|
47 |
+
|
48 |
+
this.staticMoving = false;
|
49 |
+
this.dynamicDampingFactor = 0.2;
|
50 |
+
|
51 |
+
this.minDistance = 0;
|
52 |
+
this.maxDistance = Infinity;
|
53 |
+
|
54 |
+
this.minZoom = 0;
|
55 |
+
this.maxZoom = Infinity;
|
56 |
+
|
57 |
+
this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ];
|
58 |
+
|
59 |
+
this.mouseButtons = { LEFT: MOUSE.ROTATE, MIDDLE: MOUSE.DOLLY, RIGHT: MOUSE.PAN };
|
60 |
+
this.state = _STATE.NONE;
|
61 |
+
this.keyState = _STATE.NONE;
|
62 |
+
|
63 |
+
this.target = new Vector3();
|
64 |
+
|
65 |
+
// internals
|
66 |
+
|
67 |
+
this._lastPosition = new Vector3();
|
68 |
+
this._lastZoom = 1;
|
69 |
+
this._touchZoomDistanceStart = 0;
|
70 |
+
this._touchZoomDistanceEnd = 0;
|
71 |
+
this._lastAngle = 0;
|
72 |
+
|
73 |
+
this._eye = new Vector3();
|
74 |
+
|
75 |
+
this._movePrev = new Vector2();
|
76 |
+
this._moveCurr = new Vector2();
|
77 |
+
|
78 |
+
this._lastAxis = new Vector3();
|
79 |
+
|
80 |
+
this._zoomStart = new Vector2();
|
81 |
+
this._zoomEnd = new Vector2();
|
82 |
+
|
83 |
+
this._panStart = new Vector2();
|
84 |
+
this._panEnd = new Vector2();
|
85 |
+
|
86 |
+
this._pointers = [];
|
87 |
+
this._pointerPositions = {};
|
88 |
+
|
89 |
+
// event listeners
|
90 |
+
|
91 |
+
this._onPointerMove = onPointerMove.bind( this );
|
92 |
+
this._onPointerDown = onPointerDown.bind( this );
|
93 |
+
this._onPointerUp = onPointerUp.bind( this );
|
94 |
+
this._onPointerCancel = onPointerCancel.bind( this );
|
95 |
+
this._onContextMenu = onContextMenu.bind( this );
|
96 |
+
this._onMouseWheel = onMouseWheel.bind( this );
|
97 |
+
this._onKeyDown = onKeyDown.bind( this );
|
98 |
+
this._onKeyUp = onKeyUp.bind( this );
|
99 |
+
|
100 |
+
this._onTouchStart = onTouchStart.bind( this );
|
101 |
+
this._onTouchMove = onTouchMove.bind( this );
|
102 |
+
this._onTouchEnd = onTouchEnd.bind( this );
|
103 |
+
|
104 |
+
this._onMouseDown = onMouseDown.bind( this );
|
105 |
+
this._onMouseMove = onMouseMove.bind( this );
|
106 |
+
this._onMouseUp = onMouseUp.bind( this );
|
107 |
+
|
108 |
+
// for reset
|
109 |
+
|
110 |
+
this._target0 = this.target.clone();
|
111 |
+
this._position0 = this.object.position.clone();
|
112 |
+
this._up0 = this.object.up.clone();
|
113 |
+
this._zoom0 = this.object.zoom;
|
114 |
+
|
115 |
+
if ( domElement !== null ) {
|
116 |
+
|
117 |
+
this.connect();
|
118 |
+
|
119 |
+
this.handleResize();
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
// force an update at start
|
124 |
+
this.update();
|
125 |
+
|
126 |
+
}
|
127 |
+
|
128 |
+
connect() {
|
129 |
+
|
130 |
+
window.addEventListener( 'keydown', this._onKeyDown );
|
131 |
+
window.addEventListener( 'keyup', this._onKeyUp );
|
132 |
+
|
133 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
134 |
+
this.domElement.addEventListener( 'pointercancel', this._onPointerCancel );
|
135 |
+
this.domElement.addEventListener( 'wheel', this._onMouseWheel, { passive: false } );
|
136 |
+
this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
|
137 |
+
|
138 |
+
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
139 |
+
|
140 |
+
}
|
141 |
+
|
142 |
+
disconnect() {
|
143 |
+
|
144 |
+
window.removeEventListener( 'keydown', this._onKeyDown );
|
145 |
+
window.removeEventListener( 'keyup', this._onKeyUp );
|
146 |
+
|
147 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
148 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
149 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
150 |
+
this.domElement.removeEventListener( 'pointercancel', this._onPointerCancel );
|
151 |
+
this.domElement.removeEventListener( 'wheel', this._onMouseWheel );
|
152 |
+
this.domElement.removeEventListener( 'contextmenu', this._onContextMenu );
|
153 |
+
|
154 |
+
this.domElement.style.touchAction = 'auto'; // disable touch scroll
|
155 |
+
|
156 |
+
}
|
157 |
+
|
158 |
+
dispose() {
|
159 |
+
|
160 |
+
this.disconnect();
|
161 |
+
|
162 |
+
}
|
163 |
+
|
164 |
+
handleResize() {
|
165 |
+
|
166 |
+
const box = this.domElement.getBoundingClientRect();
|
167 |
+
// adjustments come from similar code in the jquery offset() function
|
168 |
+
const d = this.domElement.ownerDocument.documentElement;
|
169 |
+
|
170 |
+
this.screen.left = box.left + window.pageXOffset - d.clientLeft;
|
171 |
+
this.screen.top = box.top + window.pageYOffset - d.clientTop;
|
172 |
+
this.screen.width = box.width;
|
173 |
+
this.screen.height = box.height;
|
174 |
+
|
175 |
+
}
|
176 |
+
|
177 |
+
update() {
|
178 |
+
|
179 |
+
this._eye.subVectors( this.object.position, this.target );
|
180 |
+
|
181 |
+
if ( ! this.noRotate ) {
|
182 |
+
|
183 |
+
this._rotateCamera();
|
184 |
+
|
185 |
+
}
|
186 |
+
|
187 |
+
if ( ! this.noZoom ) {
|
188 |
+
|
189 |
+
this._zoomCamera();
|
190 |
+
|
191 |
+
}
|
192 |
+
|
193 |
+
if ( ! this.noPan ) {
|
194 |
+
|
195 |
+
this._panCamera();
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
this.object.position.addVectors( this.target, this._eye );
|
200 |
+
|
201 |
+
if ( this.object.isPerspectiveCamera ) {
|
202 |
+
|
203 |
+
this._checkDistances();
|
204 |
+
|
205 |
+
this.object.lookAt( this.target );
|
206 |
+
|
207 |
+
if ( this._lastPosition.distanceToSquared( this.object.position ) > _EPS ) {
|
208 |
+
|
209 |
+
this.dispatchEvent( _changeEvent );
|
210 |
+
|
211 |
+
this._lastPosition.copy( this.object.position );
|
212 |
+
|
213 |
+
}
|
214 |
+
|
215 |
+
} else if ( this.object.isOrthographicCamera ) {
|
216 |
+
|
217 |
+
this.object.lookAt( this.target );
|
218 |
+
|
219 |
+
if ( this._lastPosition.distanceToSquared( this.object.position ) > _EPS || this._lastZoom !== this.object.zoom ) {
|
220 |
+
|
221 |
+
this.dispatchEvent( _changeEvent );
|
222 |
+
|
223 |
+
this._lastPosition.copy( this.object.position );
|
224 |
+
this._lastZoom = this.object.zoom;
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
} else {
|
229 |
+
|
230 |
+
console.warn( 'THREE.TrackballControls: Unsupported camera type.' );
|
231 |
+
|
232 |
+
}
|
233 |
+
|
234 |
+
}
|
235 |
+
|
236 |
+
reset() {
|
237 |
+
|
238 |
+
this.state = _STATE.NONE;
|
239 |
+
this.keyState = _STATE.NONE;
|
240 |
+
|
241 |
+
this.target.copy( this._target0 );
|
242 |
+
this.object.position.copy( this._position0 );
|
243 |
+
this.object.up.copy( this._up0 );
|
244 |
+
this.object.zoom = this._zoom0;
|
245 |
+
|
246 |
+
this.object.updateProjectionMatrix();
|
247 |
+
|
248 |
+
this._eye.subVectors( this.object.position, this.target );
|
249 |
+
|
250 |
+
this.object.lookAt( this.target );
|
251 |
+
|
252 |
+
this.dispatchEvent( _changeEvent );
|
253 |
+
|
254 |
+
this._lastPosition.copy( this.object.position );
|
255 |
+
this._lastZoom = this.object.zoom;
|
256 |
+
|
257 |
+
}
|
258 |
+
|
259 |
+
_panCamera() {
|
260 |
+
|
261 |
+
_mouseChange.copy( this._panEnd ).sub( this._panStart );
|
262 |
+
|
263 |
+
if ( _mouseChange.lengthSq() ) {
|
264 |
+
|
265 |
+
if ( this.object.isOrthographicCamera ) {
|
266 |
+
|
267 |
+
const scale_x = ( this.object.right - this.object.left ) / this.object.zoom / this.domElement.clientWidth;
|
268 |
+
const scale_y = ( this.object.top - this.object.bottom ) / this.object.zoom / this.domElement.clientWidth;
|
269 |
+
|
270 |
+
_mouseChange.x *= scale_x;
|
271 |
+
_mouseChange.y *= scale_y;
|
272 |
+
|
273 |
+
}
|
274 |
+
|
275 |
+
_mouseChange.multiplyScalar( this._eye.length() * this.panSpeed );
|
276 |
+
|
277 |
+
_pan.copy( this._eye ).cross( this.object.up ).setLength( _mouseChange.x );
|
278 |
+
_pan.add( _objectUp.copy( this.object.up ).setLength( _mouseChange.y ) );
|
279 |
+
|
280 |
+
this.object.position.add( _pan );
|
281 |
+
this.target.add( _pan );
|
282 |
+
|
283 |
+
if ( this.staticMoving ) {
|
284 |
+
|
285 |
+
this._panStart.copy( this._panEnd );
|
286 |
+
|
287 |
+
} else {
|
288 |
+
|
289 |
+
this._panStart.add( _mouseChange.subVectors( this._panEnd, this._panStart ).multiplyScalar( this.dynamicDampingFactor ) );
|
290 |
+
|
291 |
+
}
|
292 |
+
|
293 |
+
}
|
294 |
+
|
295 |
+
}
|
296 |
+
|
297 |
+
_rotateCamera() {
|
298 |
+
|
299 |
+
_moveDirection.set( this._moveCurr.x - this._movePrev.x, this._moveCurr.y - this._movePrev.y, 0 );
|
300 |
+
let angle = _moveDirection.length();
|
301 |
+
|
302 |
+
if ( angle ) {
|
303 |
+
|
304 |
+
this._eye.copy( this.object.position ).sub( this.target );
|
305 |
+
|
306 |
+
_eyeDirection.copy( this._eye ).normalize();
|
307 |
+
_objectUpDirection.copy( this.object.up ).normalize();
|
308 |
+
_objectSidewaysDirection.crossVectors( _objectUpDirection, _eyeDirection ).normalize();
|
309 |
+
|
310 |
+
_objectUpDirection.setLength( this._moveCurr.y - this._movePrev.y );
|
311 |
+
_objectSidewaysDirection.setLength( this._moveCurr.x - this._movePrev.x );
|
312 |
+
|
313 |
+
_moveDirection.copy( _objectUpDirection.add( _objectSidewaysDirection ) );
|
314 |
+
|
315 |
+
_axis.crossVectors( _moveDirection, this._eye ).normalize();
|
316 |
+
|
317 |
+
angle *= this.rotateSpeed;
|
318 |
+
_quaternion.setFromAxisAngle( _axis, angle );
|
319 |
+
|
320 |
+
this._eye.applyQuaternion( _quaternion );
|
321 |
+
this.object.up.applyQuaternion( _quaternion );
|
322 |
+
|
323 |
+
this._lastAxis.copy( _axis );
|
324 |
+
this._lastAngle = angle;
|
325 |
+
|
326 |
+
} else if ( ! this.staticMoving && this._lastAngle ) {
|
327 |
+
|
328 |
+
this._lastAngle *= Math.sqrt( 1.0 - this.dynamicDampingFactor );
|
329 |
+
this._eye.copy( this.object.position ).sub( this.target );
|
330 |
+
_quaternion.setFromAxisAngle( this._lastAxis, this._lastAngle );
|
331 |
+
this._eye.applyQuaternion( _quaternion );
|
332 |
+
this.object.up.applyQuaternion( _quaternion );
|
333 |
+
|
334 |
+
}
|
335 |
+
|
336 |
+
this._movePrev.copy( this._moveCurr );
|
337 |
+
|
338 |
+
}
|
339 |
+
|
340 |
+
_zoomCamera() {
|
341 |
+
|
342 |
+
let factor;
|
343 |
+
|
344 |
+
if ( this.state === _STATE.TOUCH_ZOOM_PAN ) {
|
345 |
+
|
346 |
+
factor = this._touchZoomDistanceStart / this._touchZoomDistanceEnd;
|
347 |
+
this._touchZoomDistanceStart = this._touchZoomDistanceEnd;
|
348 |
+
|
349 |
+
if ( this.object.isPerspectiveCamera ) {
|
350 |
+
|
351 |
+
this._eye.multiplyScalar( factor );
|
352 |
+
|
353 |
+
} else if ( this.object.isOrthographicCamera ) {
|
354 |
+
|
355 |
+
this.object.zoom = MathUtils.clamp( this.object.zoom / factor, this.minZoom, this.maxZoom );
|
356 |
+
|
357 |
+
if ( this._lastZoom !== this.object.zoom ) {
|
358 |
+
|
359 |
+
this.object.updateProjectionMatrix();
|
360 |
+
|
361 |
+
}
|
362 |
+
|
363 |
+
} else {
|
364 |
+
|
365 |
+
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
366 |
+
|
367 |
+
}
|
368 |
+
|
369 |
+
} else {
|
370 |
+
|
371 |
+
factor = 1.0 + ( this._zoomEnd.y - this._zoomStart.y ) * this.zoomSpeed;
|
372 |
+
|
373 |
+
if ( factor !== 1.0 && factor > 0.0 ) {
|
374 |
+
|
375 |
+
if ( this.object.isPerspectiveCamera ) {
|
376 |
+
|
377 |
+
this._eye.multiplyScalar( factor );
|
378 |
+
|
379 |
+
} else if ( this.object.isOrthographicCamera ) {
|
380 |
+
|
381 |
+
this.object.zoom = MathUtils.clamp( this.object.zoom / factor, this.minZoom, this.maxZoom );
|
382 |
+
|
383 |
+
if ( this._lastZoom !== this.object.zoom ) {
|
384 |
+
|
385 |
+
this.object.updateProjectionMatrix();
|
386 |
+
|
387 |
+
}
|
388 |
+
|
389 |
+
} else {
|
390 |
+
|
391 |
+
console.warn( 'THREE.TrackballControls: Unsupported camera type' );
|
392 |
+
|
393 |
+
}
|
394 |
+
|
395 |
+
}
|
396 |
+
|
397 |
+
if ( this.staticMoving ) {
|
398 |
+
|
399 |
+
this._zoomStart.copy( this._zoomEnd );
|
400 |
+
|
401 |
+
} else {
|
402 |
+
|
403 |
+
this._zoomStart.y += ( this._zoomEnd.y - this._zoomStart.y ) * this.dynamicDampingFactor;
|
404 |
+
|
405 |
+
}
|
406 |
+
|
407 |
+
}
|
408 |
+
|
409 |
+
}
|
410 |
+
|
411 |
+
_getMouseOnScreen( pageX, pageY ) {
|
412 |
+
|
413 |
+
_v2.set(
|
414 |
+
( pageX - this.screen.left ) / this.screen.width,
|
415 |
+
( pageY - this.screen.top ) / this.screen.height
|
416 |
+
);
|
417 |
+
|
418 |
+
return _v2;
|
419 |
+
|
420 |
+
}
|
421 |
+
|
422 |
+
_getMouseOnCircle( pageX, pageY ) {
|
423 |
+
|
424 |
+
_v2.set(
|
425 |
+
( ( pageX - this.screen.width * 0.5 - this.screen.left ) / ( this.screen.width * 0.5 ) ),
|
426 |
+
( ( this.screen.height + 2 * ( this.screen.top - pageY ) ) / this.screen.width ) // screen.width intentional
|
427 |
+
);
|
428 |
+
|
429 |
+
return _v2;
|
430 |
+
|
431 |
+
}
|
432 |
+
|
433 |
+
_addPointer( event ) {
|
434 |
+
|
435 |
+
this._pointers.push( event );
|
436 |
+
|
437 |
+
}
|
438 |
+
|
439 |
+
_removePointer( event ) {
|
440 |
+
|
441 |
+
delete this._pointerPositions[ event.pointerId ];
|
442 |
+
|
443 |
+
for ( let i = 0; i < this._pointers.length; i ++ ) {
|
444 |
+
|
445 |
+
if ( this._pointers[ i ].pointerId == event.pointerId ) {
|
446 |
+
|
447 |
+
this._pointers.splice( i, 1 );
|
448 |
+
return;
|
449 |
+
|
450 |
+
}
|
451 |
+
|
452 |
+
}
|
453 |
+
|
454 |
+
}
|
455 |
+
|
456 |
+
_trackPointer( event ) {
|
457 |
+
|
458 |
+
let position = this._pointerPositions[ event.pointerId ];
|
459 |
+
|
460 |
+
if ( position === undefined ) {
|
461 |
+
|
462 |
+
position = new Vector2();
|
463 |
+
this._pointerPositions[ event.pointerId ] = position;
|
464 |
+
|
465 |
+
}
|
466 |
+
|
467 |
+
position.set( event.pageX, event.pageY );
|
468 |
+
|
469 |
+
}
|
470 |
+
|
471 |
+
_getSecondPointerPosition( event ) {
|
472 |
+
|
473 |
+
const pointer = ( event.pointerId === this._pointers[ 0 ].pointerId ) ? this._pointers[ 1 ] : this._pointers[ 0 ];
|
474 |
+
|
475 |
+
return this._pointerPositions[ pointer.pointerId ];
|
476 |
+
|
477 |
+
}
|
478 |
+
|
479 |
+
_checkDistances() {
|
480 |
+
|
481 |
+
if ( ! this.noZoom || ! this.noPan ) {
|
482 |
+
|
483 |
+
if ( this._eye.lengthSq() > this.maxDistance * this.maxDistance ) {
|
484 |
+
|
485 |
+
this.object.position.addVectors( this.target, this._eye.setLength( this.maxDistance ) );
|
486 |
+
this._zoomStart.copy( this._zoomEnd );
|
487 |
+
|
488 |
+
}
|
489 |
+
|
490 |
+
if ( this._eye.lengthSq() < this.minDistance * this.minDistance ) {
|
491 |
+
|
492 |
+
this.object.position.addVectors( this.target, this._eye.setLength( this.minDistance ) );
|
493 |
+
this._zoomStart.copy( this._zoomEnd );
|
494 |
+
|
495 |
+
}
|
496 |
+
|
497 |
+
}
|
498 |
+
|
499 |
+
}
|
500 |
+
|
501 |
+
}
|
502 |
+
|
503 |
+
function onPointerDown( event ) {
|
504 |
+
|
505 |
+
if ( this.enabled === false ) return;
|
506 |
+
|
507 |
+
if ( this._pointers.length === 0 ) {
|
508 |
+
|
509 |
+
this.domElement.setPointerCapture( event.pointerId );
|
510 |
+
|
511 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
512 |
+
this.domElement.addEventListener( 'pointerup', this._onPointerUp );
|
513 |
+
|
514 |
+
}
|
515 |
+
|
516 |
+
//
|
517 |
+
|
518 |
+
this._addPointer( event );
|
519 |
+
|
520 |
+
if ( event.pointerType === 'touch' ) {
|
521 |
+
|
522 |
+
this._onTouchStart( event );
|
523 |
+
|
524 |
+
} else {
|
525 |
+
|
526 |
+
this._onMouseDown( event );
|
527 |
+
|
528 |
+
}
|
529 |
+
|
530 |
+
}
|
531 |
+
|
532 |
+
function onPointerMove( event ) {
|
533 |
+
|
534 |
+
if ( this.enabled === false ) return;
|
535 |
+
|
536 |
+
if ( event.pointerType === 'touch' ) {
|
537 |
+
|
538 |
+
this._onTouchMove( event );
|
539 |
+
|
540 |
+
} else {
|
541 |
+
|
542 |
+
this._onMouseMove( event );
|
543 |
+
|
544 |
+
}
|
545 |
+
|
546 |
+
}
|
547 |
+
|
548 |
+
function onPointerUp( event ) {
|
549 |
+
|
550 |
+
if ( this.enabled === false ) return;
|
551 |
+
|
552 |
+
if ( event.pointerType === 'touch' ) {
|
553 |
+
|
554 |
+
this._onTouchEnd( event );
|
555 |
+
|
556 |
+
} else {
|
557 |
+
|
558 |
+
this._onMouseUp();
|
559 |
+
|
560 |
+
}
|
561 |
+
|
562 |
+
//
|
563 |
+
|
564 |
+
this._removePointer( event );
|
565 |
+
|
566 |
+
if ( this._pointers.length === 0 ) {
|
567 |
+
|
568 |
+
this.domElement.releasePointerCapture( event.pointerId );
|
569 |
+
|
570 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
571 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
572 |
+
|
573 |
+
}
|
574 |
+
|
575 |
+
}
|
576 |
+
|
577 |
+
function onPointerCancel( event ) {
|
578 |
+
|
579 |
+
this._removePointer( event );
|
580 |
+
|
581 |
+
}
|
582 |
+
|
583 |
+
function onKeyUp() {
|
584 |
+
|
585 |
+
if ( this.enabled === false ) return;
|
586 |
+
|
587 |
+
this.keyState = _STATE.NONE;
|
588 |
+
|
589 |
+
window.addEventListener( 'keydown', this._onKeyDown );
|
590 |
+
|
591 |
+
}
|
592 |
+
|
593 |
+
function onKeyDown( event ) {
|
594 |
+
|
595 |
+
if ( this.enabled === false ) return;
|
596 |
+
|
597 |
+
window.removeEventListener( 'keydown', this._onKeyDown );
|
598 |
+
|
599 |
+
if ( this.keyState !== _STATE.NONE ) {
|
600 |
+
|
601 |
+
return;
|
602 |
+
|
603 |
+
} else if ( event.code === this.keys[ _STATE.ROTATE ] && ! this.noRotate ) {
|
604 |
+
|
605 |
+
this.keyState = _STATE.ROTATE;
|
606 |
+
|
607 |
+
} else if ( event.code === this.keys[ _STATE.ZOOM ] && ! this.noZoom ) {
|
608 |
+
|
609 |
+
this.keyState = _STATE.ZOOM;
|
610 |
+
|
611 |
+
} else if ( event.code === this.keys[ _STATE.PAN ] && ! this.noPan ) {
|
612 |
+
|
613 |
+
this.keyState = _STATE.PAN;
|
614 |
+
|
615 |
+
}
|
616 |
+
|
617 |
+
}
|
618 |
+
|
619 |
+
function onMouseDown( event ) {
|
620 |
+
|
621 |
+
let mouseAction;
|
622 |
+
|
623 |
+
switch ( event.button ) {
|
624 |
+
|
625 |
+
case 0:
|
626 |
+
mouseAction = this.mouseButtons.LEFT;
|
627 |
+
break;
|
628 |
+
|
629 |
+
case 1:
|
630 |
+
mouseAction = this.mouseButtons.MIDDLE;
|
631 |
+
break;
|
632 |
+
|
633 |
+
case 2:
|
634 |
+
mouseAction = this.mouseButtons.RIGHT;
|
635 |
+
break;
|
636 |
+
|
637 |
+
default:
|
638 |
+
mouseAction = - 1;
|
639 |
+
|
640 |
+
}
|
641 |
+
|
642 |
+
switch ( mouseAction ) {
|
643 |
+
|
644 |
+
case MOUSE.DOLLY:
|
645 |
+
this.state = _STATE.ZOOM;
|
646 |
+
break;
|
647 |
+
|
648 |
+
case MOUSE.ROTATE:
|
649 |
+
this.state = _STATE.ROTATE;
|
650 |
+
break;
|
651 |
+
|
652 |
+
case MOUSE.PAN:
|
653 |
+
this.state = _STATE.PAN;
|
654 |
+
break;
|
655 |
+
|
656 |
+
default:
|
657 |
+
this.state = _STATE.NONE;
|
658 |
+
|
659 |
+
}
|
660 |
+
|
661 |
+
const state = ( this.keyState !== _STATE.NONE ) ? this.keyState : this.state;
|
662 |
+
|
663 |
+
if ( state === _STATE.ROTATE && ! this.noRotate ) {
|
664 |
+
|
665 |
+
this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) );
|
666 |
+
this._movePrev.copy( this._moveCurr );
|
667 |
+
|
668 |
+
} else if ( state === _STATE.ZOOM && ! this.noZoom ) {
|
669 |
+
|
670 |
+
this._zoomStart.copy( this._getMouseOnScreen( event.pageX, event.pageY ) );
|
671 |
+
this._zoomEnd.copy( this._zoomStart );
|
672 |
+
|
673 |
+
} else if ( state === _STATE.PAN && ! this.noPan ) {
|
674 |
+
|
675 |
+
this._panStart.copy( this._getMouseOnScreen( event.pageX, event.pageY ) );
|
676 |
+
this._panEnd.copy( this._panStart );
|
677 |
+
|
678 |
+
}
|
679 |
+
|
680 |
+
this.dispatchEvent( _startEvent );
|
681 |
+
|
682 |
+
}
|
683 |
+
|
684 |
+
function onMouseMove( event ) {
|
685 |
+
|
686 |
+
const state = ( this.keyState !== _STATE.NONE ) ? this.keyState : this.state;
|
687 |
+
|
688 |
+
if ( state === _STATE.ROTATE && ! this.noRotate ) {
|
689 |
+
|
690 |
+
this._movePrev.copy( this._moveCurr );
|
691 |
+
this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) );
|
692 |
+
|
693 |
+
} else if ( state === _STATE.ZOOM && ! this.noZoom ) {
|
694 |
+
|
695 |
+
this._zoomEnd.copy( this._getMouseOnScreen( event.pageX, event.pageY ) );
|
696 |
+
|
697 |
+
} else if ( state === _STATE.PAN && ! this.noPan ) {
|
698 |
+
|
699 |
+
this._panEnd.copy( this._getMouseOnScreen( event.pageX, event.pageY ) );
|
700 |
+
|
701 |
+
}
|
702 |
+
|
703 |
+
}
|
704 |
+
|
705 |
+
function onMouseUp() {
|
706 |
+
|
707 |
+
this.state = _STATE.NONE;
|
708 |
+
|
709 |
+
this.dispatchEvent( _endEvent );
|
710 |
+
|
711 |
+
}
|
712 |
+
|
713 |
+
function onMouseWheel( event ) {
|
714 |
+
|
715 |
+
if ( this.enabled === false ) return;
|
716 |
+
|
717 |
+
if ( this.noZoom === true ) return;
|
718 |
+
|
719 |
+
event.preventDefault();
|
720 |
+
|
721 |
+
switch ( event.deltaMode ) {
|
722 |
+
|
723 |
+
case 2:
|
724 |
+
// Zoom in pages
|
725 |
+
this._zoomStart.y -= event.deltaY * 0.025;
|
726 |
+
break;
|
727 |
+
|
728 |
+
case 1:
|
729 |
+
// Zoom in lines
|
730 |
+
this._zoomStart.y -= event.deltaY * 0.01;
|
731 |
+
break;
|
732 |
+
|
733 |
+
default:
|
734 |
+
// undefined, 0, assume pixels
|
735 |
+
this._zoomStart.y -= event.deltaY * 0.00025;
|
736 |
+
break;
|
737 |
+
|
738 |
+
}
|
739 |
+
|
740 |
+
this.dispatchEvent( _startEvent );
|
741 |
+
this.dispatchEvent( _endEvent );
|
742 |
+
|
743 |
+
}
|
744 |
+
|
745 |
+
function onContextMenu( event ) {
|
746 |
+
|
747 |
+
if ( this.enabled === false ) return;
|
748 |
+
|
749 |
+
event.preventDefault();
|
750 |
+
|
751 |
+
}
|
752 |
+
|
753 |
+
function onTouchStart( event ) {
|
754 |
+
|
755 |
+
this._trackPointer( event );
|
756 |
+
|
757 |
+
switch ( this._pointers.length ) {
|
758 |
+
|
759 |
+
case 1:
|
760 |
+
this.state = _STATE.TOUCH_ROTATE;
|
761 |
+
this._moveCurr.copy( this._getMouseOnCircle( this._pointers[ 0 ].pageX, this._pointers[ 0 ].pageY ) );
|
762 |
+
this._movePrev.copy( this._moveCurr );
|
763 |
+
break;
|
764 |
+
|
765 |
+
default: // 2 or more
|
766 |
+
this.state = _STATE.TOUCH_ZOOM_PAN;
|
767 |
+
const dx = this._pointers[ 0 ].pageX - this._pointers[ 1 ].pageX;
|
768 |
+
const dy = this._pointers[ 0 ].pageY - this._pointers[ 1 ].pageY;
|
769 |
+
this._touchZoomDistanceEnd = this._touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
|
770 |
+
|
771 |
+
const x = ( this._pointers[ 0 ].pageX + this._pointers[ 1 ].pageX ) / 2;
|
772 |
+
const y = ( this._pointers[ 0 ].pageY + this._pointers[ 1 ].pageY ) / 2;
|
773 |
+
this._panStart.copy( this._getMouseOnScreen( x, y ) );
|
774 |
+
this._panEnd.copy( this._panStart );
|
775 |
+
break;
|
776 |
+
|
777 |
+
}
|
778 |
+
|
779 |
+
this.dispatchEvent( _startEvent );
|
780 |
+
|
781 |
+
}
|
782 |
+
|
783 |
+
function onTouchMove( event ) {
|
784 |
+
|
785 |
+
this._trackPointer( event );
|
786 |
+
|
787 |
+
switch ( this._pointers.length ) {
|
788 |
+
|
789 |
+
case 1:
|
790 |
+
this._movePrev.copy( this._moveCurr );
|
791 |
+
this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) );
|
792 |
+
break;
|
793 |
+
|
794 |
+
default: // 2 or more
|
795 |
+
|
796 |
+
const position = this._getSecondPointerPosition( event );
|
797 |
+
|
798 |
+
const dx = event.pageX - position.x;
|
799 |
+
const dy = event.pageY - position.y;
|
800 |
+
this._touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
|
801 |
+
|
802 |
+
const x = ( event.pageX + position.x ) / 2;
|
803 |
+
const y = ( event.pageY + position.y ) / 2;
|
804 |
+
this._panEnd.copy( this._getMouseOnScreen( x, y ) );
|
805 |
+
break;
|
806 |
+
|
807 |
+
}
|
808 |
+
|
809 |
+
}
|
810 |
+
|
811 |
+
function onTouchEnd( event ) {
|
812 |
+
|
813 |
+
switch ( this._pointers.length ) {
|
814 |
+
|
815 |
+
case 0:
|
816 |
+
this.state = _STATE.NONE;
|
817 |
+
break;
|
818 |
+
|
819 |
+
case 1:
|
820 |
+
this.state = _STATE.TOUCH_ROTATE;
|
821 |
+
this._moveCurr.copy( this._getMouseOnCircle( event.pageX, event.pageY ) );
|
822 |
+
this._movePrev.copy( this._moveCurr );
|
823 |
+
break;
|
824 |
+
|
825 |
+
case 2:
|
826 |
+
this.state = _STATE.TOUCH_ZOOM_PAN;
|
827 |
+
|
828 |
+
for ( let i = 0; i < this._pointers.length; i ++ ) {
|
829 |
+
|
830 |
+
if ( this._pointers[ i ].pointerId !== event.pointerId ) {
|
831 |
+
|
832 |
+
const position = this._pointerPositions[ this._pointers[ i ].pointerId ];
|
833 |
+
this._moveCurr.copy( this._getMouseOnCircle( position.x, position.y ) );
|
834 |
+
this._movePrev.copy( this._moveCurr );
|
835 |
+
break;
|
836 |
+
|
837 |
+
}
|
838 |
+
|
839 |
+
}
|
840 |
+
|
841 |
+
break;
|
842 |
+
|
843 |
+
}
|
844 |
+
|
845 |
+
this.dispatchEvent( _endEvent );
|
846 |
+
|
847 |
+
}
|
848 |
+
|
849 |
+
export { TrackballControls };
|
libs/three.js/0.172.0/jsm/controls/TransformControls.js
ADDED
@@ -0,0 +1,1624 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BoxGeometry,
|
3 |
+
BufferGeometry,
|
4 |
+
Controls,
|
5 |
+
CylinderGeometry,
|
6 |
+
DoubleSide,
|
7 |
+
Euler,
|
8 |
+
Float32BufferAttribute,
|
9 |
+
Line,
|
10 |
+
LineBasicMaterial,
|
11 |
+
Matrix4,
|
12 |
+
Mesh,
|
13 |
+
MeshBasicMaterial,
|
14 |
+
Object3D,
|
15 |
+
OctahedronGeometry,
|
16 |
+
PlaneGeometry,
|
17 |
+
Quaternion,
|
18 |
+
Raycaster,
|
19 |
+
SphereGeometry,
|
20 |
+
TorusGeometry,
|
21 |
+
Vector3
|
22 |
+
} from 'three';
|
23 |
+
|
24 |
+
const _raycaster = new Raycaster();
|
25 |
+
|
26 |
+
const _tempVector = new Vector3();
|
27 |
+
const _tempVector2 = new Vector3();
|
28 |
+
const _tempQuaternion = new Quaternion();
|
29 |
+
const _unit = {
|
30 |
+
X: new Vector3( 1, 0, 0 ),
|
31 |
+
Y: new Vector3( 0, 1, 0 ),
|
32 |
+
Z: new Vector3( 0, 0, 1 )
|
33 |
+
};
|
34 |
+
|
35 |
+
const _changeEvent = { type: 'change' };
|
36 |
+
const _mouseDownEvent = { type: 'mouseDown', mode: null };
|
37 |
+
const _mouseUpEvent = { type: 'mouseUp', mode: null };
|
38 |
+
const _objectChangeEvent = { type: 'objectChange' };
|
39 |
+
|
40 |
+
class TransformControls extends Controls {
|
41 |
+
|
42 |
+
constructor( camera, domElement = null ) {
|
43 |
+
|
44 |
+
super( undefined, domElement );
|
45 |
+
|
46 |
+
const root = new TransformControlsRoot( this );
|
47 |
+
this._root = root;
|
48 |
+
|
49 |
+
const gizmo = new TransformControlsGizmo();
|
50 |
+
this._gizmo = gizmo;
|
51 |
+
root.add( gizmo );
|
52 |
+
|
53 |
+
const plane = new TransformControlsPlane();
|
54 |
+
this._plane = plane;
|
55 |
+
root.add( plane );
|
56 |
+
|
57 |
+
const scope = this;
|
58 |
+
|
59 |
+
// Defined getter, setter and store for a property
|
60 |
+
function defineProperty( propName, defaultValue ) {
|
61 |
+
|
62 |
+
let propValue = defaultValue;
|
63 |
+
|
64 |
+
Object.defineProperty( scope, propName, {
|
65 |
+
|
66 |
+
get: function () {
|
67 |
+
|
68 |
+
return propValue !== undefined ? propValue : defaultValue;
|
69 |
+
|
70 |
+
},
|
71 |
+
|
72 |
+
set: function ( value ) {
|
73 |
+
|
74 |
+
if ( propValue !== value ) {
|
75 |
+
|
76 |
+
propValue = value;
|
77 |
+
plane[ propName ] = value;
|
78 |
+
gizmo[ propName ] = value;
|
79 |
+
|
80 |
+
scope.dispatchEvent( { type: propName + '-changed', value: value } );
|
81 |
+
scope.dispatchEvent( _changeEvent );
|
82 |
+
|
83 |
+
}
|
84 |
+
|
85 |
+
}
|
86 |
+
|
87 |
+
} );
|
88 |
+
|
89 |
+
scope[ propName ] = defaultValue;
|
90 |
+
plane[ propName ] = defaultValue;
|
91 |
+
gizmo[ propName ] = defaultValue;
|
92 |
+
|
93 |
+
}
|
94 |
+
|
95 |
+
// Define properties with getters/setter
|
96 |
+
// Setting the defined property will automatically trigger change event
|
97 |
+
// Defined properties are passed down to gizmo and plane
|
98 |
+
|
99 |
+
defineProperty( 'camera', camera );
|
100 |
+
defineProperty( 'object', undefined );
|
101 |
+
defineProperty( 'enabled', true );
|
102 |
+
defineProperty( 'axis', null );
|
103 |
+
defineProperty( 'mode', 'translate' );
|
104 |
+
defineProperty( 'translationSnap', null );
|
105 |
+
defineProperty( 'rotationSnap', null );
|
106 |
+
defineProperty( 'scaleSnap', null );
|
107 |
+
defineProperty( 'space', 'world' );
|
108 |
+
defineProperty( 'size', 1 );
|
109 |
+
defineProperty( 'dragging', false );
|
110 |
+
defineProperty( 'showX', true );
|
111 |
+
defineProperty( 'showY', true );
|
112 |
+
defineProperty( 'showZ', true );
|
113 |
+
defineProperty( 'minX', - Infinity );
|
114 |
+
defineProperty( 'maxX', Infinity );
|
115 |
+
defineProperty( 'minY', - Infinity );
|
116 |
+
defineProperty( 'maxY', Infinity );
|
117 |
+
defineProperty( 'minZ', - Infinity );
|
118 |
+
defineProperty( 'maxZ', Infinity );
|
119 |
+
|
120 |
+
// Reusable utility variables
|
121 |
+
|
122 |
+
const worldPosition = new Vector3();
|
123 |
+
const worldPositionStart = new Vector3();
|
124 |
+
const worldQuaternion = new Quaternion();
|
125 |
+
const worldQuaternionStart = new Quaternion();
|
126 |
+
const cameraPosition = new Vector3();
|
127 |
+
const cameraQuaternion = new Quaternion();
|
128 |
+
const pointStart = new Vector3();
|
129 |
+
const pointEnd = new Vector3();
|
130 |
+
const rotationAxis = new Vector3();
|
131 |
+
const rotationAngle = 0;
|
132 |
+
const eye = new Vector3();
|
133 |
+
|
134 |
+
// TODO: remove properties unused in plane and gizmo
|
135 |
+
|
136 |
+
defineProperty( 'worldPosition', worldPosition );
|
137 |
+
defineProperty( 'worldPositionStart', worldPositionStart );
|
138 |
+
defineProperty( 'worldQuaternion', worldQuaternion );
|
139 |
+
defineProperty( 'worldQuaternionStart', worldQuaternionStart );
|
140 |
+
defineProperty( 'cameraPosition', cameraPosition );
|
141 |
+
defineProperty( 'cameraQuaternion', cameraQuaternion );
|
142 |
+
defineProperty( 'pointStart', pointStart );
|
143 |
+
defineProperty( 'pointEnd', pointEnd );
|
144 |
+
defineProperty( 'rotationAxis', rotationAxis );
|
145 |
+
defineProperty( 'rotationAngle', rotationAngle );
|
146 |
+
defineProperty( 'eye', eye );
|
147 |
+
|
148 |
+
this._offset = new Vector3();
|
149 |
+
this._startNorm = new Vector3();
|
150 |
+
this._endNorm = new Vector3();
|
151 |
+
this._cameraScale = new Vector3();
|
152 |
+
|
153 |
+
this._parentPosition = new Vector3();
|
154 |
+
this._parentQuaternion = new Quaternion();
|
155 |
+
this._parentQuaternionInv = new Quaternion();
|
156 |
+
this._parentScale = new Vector3();
|
157 |
+
|
158 |
+
this._worldScaleStart = new Vector3();
|
159 |
+
this._worldQuaternionInv = new Quaternion();
|
160 |
+
this._worldScale = new Vector3();
|
161 |
+
|
162 |
+
this._positionStart = new Vector3();
|
163 |
+
this._quaternionStart = new Quaternion();
|
164 |
+
this._scaleStart = new Vector3();
|
165 |
+
|
166 |
+
this._getPointer = getPointer.bind( this );
|
167 |
+
this._onPointerDown = onPointerDown.bind( this );
|
168 |
+
this._onPointerHover = onPointerHover.bind( this );
|
169 |
+
this._onPointerMove = onPointerMove.bind( this );
|
170 |
+
this._onPointerUp = onPointerUp.bind( this );
|
171 |
+
|
172 |
+
if ( domElement !== null ) {
|
173 |
+
|
174 |
+
this.connect();
|
175 |
+
|
176 |
+
}
|
177 |
+
|
178 |
+
}
|
179 |
+
|
180 |
+
connect() {
|
181 |
+
|
182 |
+
this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
|
183 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerHover );
|
184 |
+
this.domElement.addEventListener( 'pointerup', this._onPointerUp );
|
185 |
+
|
186 |
+
this.domElement.style.touchAction = 'none'; // disable touch scroll
|
187 |
+
|
188 |
+
}
|
189 |
+
|
190 |
+
disconnect() {
|
191 |
+
|
192 |
+
this.domElement.removeEventListener( 'pointerdown', this._onPointerDown );
|
193 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerHover );
|
194 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
195 |
+
this.domElement.removeEventListener( 'pointerup', this._onPointerUp );
|
196 |
+
|
197 |
+
this.domElement.style.touchAction = 'auto';
|
198 |
+
|
199 |
+
}
|
200 |
+
|
201 |
+
getHelper() {
|
202 |
+
|
203 |
+
return this._root;
|
204 |
+
|
205 |
+
}
|
206 |
+
|
207 |
+
pointerHover( pointer ) {
|
208 |
+
|
209 |
+
if ( this.object === undefined || this.dragging === true ) return;
|
210 |
+
|
211 |
+
if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
|
212 |
+
|
213 |
+
const intersect = intersectObjectWithRay( this._gizmo.picker[ this.mode ], _raycaster );
|
214 |
+
|
215 |
+
if ( intersect ) {
|
216 |
+
|
217 |
+
this.axis = intersect.object.name;
|
218 |
+
|
219 |
+
} else {
|
220 |
+
|
221 |
+
this.axis = null;
|
222 |
+
|
223 |
+
}
|
224 |
+
|
225 |
+
}
|
226 |
+
|
227 |
+
pointerDown( pointer ) {
|
228 |
+
|
229 |
+
if ( this.object === undefined || this.dragging === true || ( pointer != null && pointer.button !== 0 ) ) return;
|
230 |
+
|
231 |
+
if ( this.axis !== null ) {
|
232 |
+
|
233 |
+
if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
|
234 |
+
|
235 |
+
const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
|
236 |
+
|
237 |
+
if ( planeIntersect ) {
|
238 |
+
|
239 |
+
this.object.updateMatrixWorld();
|
240 |
+
this.object.parent.updateMatrixWorld();
|
241 |
+
|
242 |
+
this._positionStart.copy( this.object.position );
|
243 |
+
this._quaternionStart.copy( this.object.quaternion );
|
244 |
+
this._scaleStart.copy( this.object.scale );
|
245 |
+
|
246 |
+
this.object.matrixWorld.decompose( this.worldPositionStart, this.worldQuaternionStart, this._worldScaleStart );
|
247 |
+
|
248 |
+
this.pointStart.copy( planeIntersect.point ).sub( this.worldPositionStart );
|
249 |
+
|
250 |
+
}
|
251 |
+
|
252 |
+
this.dragging = true;
|
253 |
+
_mouseDownEvent.mode = this.mode;
|
254 |
+
this.dispatchEvent( _mouseDownEvent );
|
255 |
+
|
256 |
+
}
|
257 |
+
|
258 |
+
}
|
259 |
+
|
260 |
+
pointerMove( pointer ) {
|
261 |
+
|
262 |
+
const axis = this.axis;
|
263 |
+
const mode = this.mode;
|
264 |
+
const object = this.object;
|
265 |
+
let space = this.space;
|
266 |
+
|
267 |
+
if ( mode === 'scale' ) {
|
268 |
+
|
269 |
+
space = 'local';
|
270 |
+
|
271 |
+
} else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
|
272 |
+
|
273 |
+
space = 'world';
|
274 |
+
|
275 |
+
}
|
276 |
+
|
277 |
+
if ( object === undefined || axis === null || this.dragging === false || ( pointer !== null && pointer.button !== - 1 ) ) return;
|
278 |
+
|
279 |
+
if ( pointer !== null ) _raycaster.setFromCamera( pointer, this.camera );
|
280 |
+
|
281 |
+
const planeIntersect = intersectObjectWithRay( this._plane, _raycaster, true );
|
282 |
+
|
283 |
+
if ( ! planeIntersect ) return;
|
284 |
+
|
285 |
+
this.pointEnd.copy( planeIntersect.point ).sub( this.worldPositionStart );
|
286 |
+
|
287 |
+
if ( mode === 'translate' ) {
|
288 |
+
|
289 |
+
// Apply translate
|
290 |
+
|
291 |
+
this._offset.copy( this.pointEnd ).sub( this.pointStart );
|
292 |
+
|
293 |
+
if ( space === 'local' && axis !== 'XYZ' ) {
|
294 |
+
|
295 |
+
this._offset.applyQuaternion( this._worldQuaternionInv );
|
296 |
+
|
297 |
+
}
|
298 |
+
|
299 |
+
if ( axis.indexOf( 'X' ) === - 1 ) this._offset.x = 0;
|
300 |
+
if ( axis.indexOf( 'Y' ) === - 1 ) this._offset.y = 0;
|
301 |
+
if ( axis.indexOf( 'Z' ) === - 1 ) this._offset.z = 0;
|
302 |
+
|
303 |
+
if ( space === 'local' && axis !== 'XYZ' ) {
|
304 |
+
|
305 |
+
this._offset.applyQuaternion( this._quaternionStart ).divide( this._parentScale );
|
306 |
+
|
307 |
+
} else {
|
308 |
+
|
309 |
+
this._offset.applyQuaternion( this._parentQuaternionInv ).divide( this._parentScale );
|
310 |
+
|
311 |
+
}
|
312 |
+
|
313 |
+
object.position.copy( this._offset ).add( this._positionStart );
|
314 |
+
|
315 |
+
// Apply translation snap
|
316 |
+
|
317 |
+
if ( this.translationSnap ) {
|
318 |
+
|
319 |
+
if ( space === 'local' ) {
|
320 |
+
|
321 |
+
object.position.applyQuaternion( _tempQuaternion.copy( this._quaternionStart ).invert() );
|
322 |
+
|
323 |
+
if ( axis.search( 'X' ) !== - 1 ) {
|
324 |
+
|
325 |
+
object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
|
326 |
+
|
327 |
+
}
|
328 |
+
|
329 |
+
if ( axis.search( 'Y' ) !== - 1 ) {
|
330 |
+
|
331 |
+
object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
|
332 |
+
|
333 |
+
}
|
334 |
+
|
335 |
+
if ( axis.search( 'Z' ) !== - 1 ) {
|
336 |
+
|
337 |
+
object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
|
338 |
+
|
339 |
+
}
|
340 |
+
|
341 |
+
object.position.applyQuaternion( this._quaternionStart );
|
342 |
+
|
343 |
+
}
|
344 |
+
|
345 |
+
if ( space === 'world' ) {
|
346 |
+
|
347 |
+
if ( object.parent ) {
|
348 |
+
|
349 |
+
object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
|
350 |
+
|
351 |
+
}
|
352 |
+
|
353 |
+
if ( axis.search( 'X' ) !== - 1 ) {
|
354 |
+
|
355 |
+
object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
|
356 |
+
|
357 |
+
}
|
358 |
+
|
359 |
+
if ( axis.search( 'Y' ) !== - 1 ) {
|
360 |
+
|
361 |
+
object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
|
362 |
+
|
363 |
+
}
|
364 |
+
|
365 |
+
if ( axis.search( 'Z' ) !== - 1 ) {
|
366 |
+
|
367 |
+
object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
|
368 |
+
|
369 |
+
}
|
370 |
+
|
371 |
+
if ( object.parent ) {
|
372 |
+
|
373 |
+
object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
|
374 |
+
|
375 |
+
}
|
376 |
+
|
377 |
+
}
|
378 |
+
|
379 |
+
}
|
380 |
+
|
381 |
+
object.position.x = Math.max( this.minX, Math.min( this.maxX, object.position.x ) );
|
382 |
+
object.position.y = Math.max( this.minY, Math.min( this.maxY, object.position.y ) );
|
383 |
+
object.position.z = Math.max( this.minZ, Math.min( this.maxZ, object.position.z ) );
|
384 |
+
|
385 |
+
} else if ( mode === 'scale' ) {
|
386 |
+
|
387 |
+
if ( axis.search( 'XYZ' ) !== - 1 ) {
|
388 |
+
|
389 |
+
let d = this.pointEnd.length() / this.pointStart.length();
|
390 |
+
|
391 |
+
if ( this.pointEnd.dot( this.pointStart ) < 0 ) d *= - 1;
|
392 |
+
|
393 |
+
_tempVector2.set( d, d, d );
|
394 |
+
|
395 |
+
} else {
|
396 |
+
|
397 |
+
_tempVector.copy( this.pointStart );
|
398 |
+
_tempVector2.copy( this.pointEnd );
|
399 |
+
|
400 |
+
_tempVector.applyQuaternion( this._worldQuaternionInv );
|
401 |
+
_tempVector2.applyQuaternion( this._worldQuaternionInv );
|
402 |
+
|
403 |
+
_tempVector2.divide( _tempVector );
|
404 |
+
|
405 |
+
if ( axis.search( 'X' ) === - 1 ) {
|
406 |
+
|
407 |
+
_tempVector2.x = 1;
|
408 |
+
|
409 |
+
}
|
410 |
+
|
411 |
+
if ( axis.search( 'Y' ) === - 1 ) {
|
412 |
+
|
413 |
+
_tempVector2.y = 1;
|
414 |
+
|
415 |
+
}
|
416 |
+
|
417 |
+
if ( axis.search( 'Z' ) === - 1 ) {
|
418 |
+
|
419 |
+
_tempVector2.z = 1;
|
420 |
+
|
421 |
+
}
|
422 |
+
|
423 |
+
}
|
424 |
+
|
425 |
+
// Apply scale
|
426 |
+
|
427 |
+
object.scale.copy( this._scaleStart ).multiply( _tempVector2 );
|
428 |
+
|
429 |
+
if ( this.scaleSnap ) {
|
430 |
+
|
431 |
+
if ( axis.search( 'X' ) !== - 1 ) {
|
432 |
+
|
433 |
+
object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
|
434 |
+
|
435 |
+
}
|
436 |
+
|
437 |
+
if ( axis.search( 'Y' ) !== - 1 ) {
|
438 |
+
|
439 |
+
object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
|
440 |
+
|
441 |
+
}
|
442 |
+
|
443 |
+
if ( axis.search( 'Z' ) !== - 1 ) {
|
444 |
+
|
445 |
+
object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
|
446 |
+
|
447 |
+
}
|
448 |
+
|
449 |
+
}
|
450 |
+
|
451 |
+
} else if ( mode === 'rotate' ) {
|
452 |
+
|
453 |
+
this._offset.copy( this.pointEnd ).sub( this.pointStart );
|
454 |
+
|
455 |
+
const ROTATION_SPEED = 20 / this.worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
|
456 |
+
|
457 |
+
let _inPlaneRotation = false;
|
458 |
+
|
459 |
+
if ( axis === 'XYZE' ) {
|
460 |
+
|
461 |
+
this.rotationAxis.copy( this._offset ).cross( this.eye ).normalize();
|
462 |
+
this.rotationAngle = this._offset.dot( _tempVector.copy( this.rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
|
463 |
+
|
464 |
+
} else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
|
465 |
+
|
466 |
+
this.rotationAxis.copy( _unit[ axis ] );
|
467 |
+
|
468 |
+
_tempVector.copy( _unit[ axis ] );
|
469 |
+
|
470 |
+
if ( space === 'local' ) {
|
471 |
+
|
472 |
+
_tempVector.applyQuaternion( this.worldQuaternion );
|
473 |
+
|
474 |
+
}
|
475 |
+
|
476 |
+
_tempVector.cross( this.eye );
|
477 |
+
|
478 |
+
// When _tempVector is 0 after cross with this.eye the vectors are parallel and should use in-plane rotation logic.
|
479 |
+
if ( _tempVector.length() === 0 ) {
|
480 |
+
|
481 |
+
_inPlaneRotation = true;
|
482 |
+
|
483 |
+
} else {
|
484 |
+
|
485 |
+
this.rotationAngle = this._offset.dot( _tempVector.normalize() ) * ROTATION_SPEED;
|
486 |
+
|
487 |
+
}
|
488 |
+
|
489 |
+
|
490 |
+
}
|
491 |
+
|
492 |
+
if ( axis === 'E' || _inPlaneRotation ) {
|
493 |
+
|
494 |
+
this.rotationAxis.copy( this.eye );
|
495 |
+
this.rotationAngle = this.pointEnd.angleTo( this.pointStart );
|
496 |
+
|
497 |
+
this._startNorm.copy( this.pointStart ).normalize();
|
498 |
+
this._endNorm.copy( this.pointEnd ).normalize();
|
499 |
+
|
500 |
+
this.rotationAngle *= ( this._endNorm.cross( this._startNorm ).dot( this.eye ) < 0 ? 1 : - 1 );
|
501 |
+
|
502 |
+
}
|
503 |
+
|
504 |
+
// Apply rotation snap
|
505 |
+
|
506 |
+
if ( this.rotationSnap ) this.rotationAngle = Math.round( this.rotationAngle / this.rotationSnap ) * this.rotationSnap;
|
507 |
+
|
508 |
+
// Apply rotate
|
509 |
+
if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
|
510 |
+
|
511 |
+
object.quaternion.copy( this._quaternionStart );
|
512 |
+
object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) ).normalize();
|
513 |
+
|
514 |
+
} else {
|
515 |
+
|
516 |
+
this.rotationAxis.applyQuaternion( this._parentQuaternionInv );
|
517 |
+
object.quaternion.copy( _tempQuaternion.setFromAxisAngle( this.rotationAxis, this.rotationAngle ) );
|
518 |
+
object.quaternion.multiply( this._quaternionStart ).normalize();
|
519 |
+
|
520 |
+
}
|
521 |
+
|
522 |
+
}
|
523 |
+
|
524 |
+
this.dispatchEvent( _changeEvent );
|
525 |
+
this.dispatchEvent( _objectChangeEvent );
|
526 |
+
|
527 |
+
}
|
528 |
+
|
529 |
+
pointerUp( pointer ) {
|
530 |
+
|
531 |
+
if ( pointer !== null && pointer.button !== 0 ) return;
|
532 |
+
|
533 |
+
if ( this.dragging && ( this.axis !== null ) ) {
|
534 |
+
|
535 |
+
_mouseUpEvent.mode = this.mode;
|
536 |
+
this.dispatchEvent( _mouseUpEvent );
|
537 |
+
|
538 |
+
}
|
539 |
+
|
540 |
+
this.dragging = false;
|
541 |
+
this.axis = null;
|
542 |
+
|
543 |
+
}
|
544 |
+
|
545 |
+
dispose() {
|
546 |
+
|
547 |
+
this.disconnect();
|
548 |
+
|
549 |
+
this._root.dispose();
|
550 |
+
|
551 |
+
}
|
552 |
+
|
553 |
+
// Set current object
|
554 |
+
attach( object ) {
|
555 |
+
|
556 |
+
this.object = object;
|
557 |
+
this._root.visible = true;
|
558 |
+
|
559 |
+
return this;
|
560 |
+
|
561 |
+
}
|
562 |
+
|
563 |
+
// Detach from object
|
564 |
+
detach() {
|
565 |
+
|
566 |
+
this.object = undefined;
|
567 |
+
this.axis = null;
|
568 |
+
|
569 |
+
this._root.visible = false;
|
570 |
+
|
571 |
+
return this;
|
572 |
+
|
573 |
+
}
|
574 |
+
|
575 |
+
reset() {
|
576 |
+
|
577 |
+
if ( ! this.enabled ) return;
|
578 |
+
|
579 |
+
if ( this.dragging ) {
|
580 |
+
|
581 |
+
this.object.position.copy( this._positionStart );
|
582 |
+
this.object.quaternion.copy( this._quaternionStart );
|
583 |
+
this.object.scale.copy( this._scaleStart );
|
584 |
+
|
585 |
+
this.dispatchEvent( _changeEvent );
|
586 |
+
this.dispatchEvent( _objectChangeEvent );
|
587 |
+
|
588 |
+
this.pointStart.copy( this.pointEnd );
|
589 |
+
|
590 |
+
}
|
591 |
+
|
592 |
+
}
|
593 |
+
|
594 |
+
getRaycaster() {
|
595 |
+
|
596 |
+
return _raycaster;
|
597 |
+
|
598 |
+
}
|
599 |
+
|
600 |
+
// TODO: deprecate
|
601 |
+
|
602 |
+
getMode() {
|
603 |
+
|
604 |
+
return this.mode;
|
605 |
+
|
606 |
+
}
|
607 |
+
|
608 |
+
setMode( mode ) {
|
609 |
+
|
610 |
+
this.mode = mode;
|
611 |
+
|
612 |
+
}
|
613 |
+
|
614 |
+
setTranslationSnap( translationSnap ) {
|
615 |
+
|
616 |
+
this.translationSnap = translationSnap;
|
617 |
+
|
618 |
+
}
|
619 |
+
|
620 |
+
setRotationSnap( rotationSnap ) {
|
621 |
+
|
622 |
+
this.rotationSnap = rotationSnap;
|
623 |
+
|
624 |
+
}
|
625 |
+
|
626 |
+
setScaleSnap( scaleSnap ) {
|
627 |
+
|
628 |
+
this.scaleSnap = scaleSnap;
|
629 |
+
|
630 |
+
}
|
631 |
+
|
632 |
+
setSize( size ) {
|
633 |
+
|
634 |
+
this.size = size;
|
635 |
+
|
636 |
+
}
|
637 |
+
|
638 |
+
setSpace( space ) {
|
639 |
+
|
640 |
+
this.space = space;
|
641 |
+
|
642 |
+
}
|
643 |
+
|
644 |
+
}
|
645 |
+
|
646 |
+
// mouse / touch event handlers
|
647 |
+
|
648 |
+
function getPointer( event ) {
|
649 |
+
|
650 |
+
if ( this.domElement.ownerDocument.pointerLockElement ) {
|
651 |
+
|
652 |
+
return {
|
653 |
+
x: 0,
|
654 |
+
y: 0,
|
655 |
+
button: event.button
|
656 |
+
};
|
657 |
+
|
658 |
+
} else {
|
659 |
+
|
660 |
+
const rect = this.domElement.getBoundingClientRect();
|
661 |
+
|
662 |
+
return {
|
663 |
+
x: ( event.clientX - rect.left ) / rect.width * 2 - 1,
|
664 |
+
y: - ( event.clientY - rect.top ) / rect.height * 2 + 1,
|
665 |
+
button: event.button
|
666 |
+
};
|
667 |
+
|
668 |
+
}
|
669 |
+
|
670 |
+
}
|
671 |
+
|
672 |
+
function onPointerHover( event ) {
|
673 |
+
|
674 |
+
if ( ! this.enabled ) return;
|
675 |
+
|
676 |
+
switch ( event.pointerType ) {
|
677 |
+
|
678 |
+
case 'mouse':
|
679 |
+
case 'pen':
|
680 |
+
this.pointerHover( this._getPointer( event ) );
|
681 |
+
break;
|
682 |
+
|
683 |
+
}
|
684 |
+
|
685 |
+
}
|
686 |
+
|
687 |
+
function onPointerDown( event ) {
|
688 |
+
|
689 |
+
if ( ! this.enabled ) return;
|
690 |
+
|
691 |
+
if ( ! document.pointerLockElement ) {
|
692 |
+
|
693 |
+
this.domElement.setPointerCapture( event.pointerId );
|
694 |
+
|
695 |
+
}
|
696 |
+
|
697 |
+
this.domElement.addEventListener( 'pointermove', this._onPointerMove );
|
698 |
+
|
699 |
+
this.pointerHover( this._getPointer( event ) );
|
700 |
+
this.pointerDown( this._getPointer( event ) );
|
701 |
+
|
702 |
+
}
|
703 |
+
|
704 |
+
function onPointerMove( event ) {
|
705 |
+
|
706 |
+
if ( ! this.enabled ) return;
|
707 |
+
|
708 |
+
this.pointerMove( this._getPointer( event ) );
|
709 |
+
|
710 |
+
}
|
711 |
+
|
712 |
+
function onPointerUp( event ) {
|
713 |
+
|
714 |
+
if ( ! this.enabled ) return;
|
715 |
+
|
716 |
+
this.domElement.releasePointerCapture( event.pointerId );
|
717 |
+
|
718 |
+
this.domElement.removeEventListener( 'pointermove', this._onPointerMove );
|
719 |
+
|
720 |
+
this.pointerUp( this._getPointer( event ) );
|
721 |
+
|
722 |
+
}
|
723 |
+
|
724 |
+
function intersectObjectWithRay( object, raycaster, includeInvisible ) {
|
725 |
+
|
726 |
+
const allIntersections = raycaster.intersectObject( object, true );
|
727 |
+
|
728 |
+
for ( let i = 0; i < allIntersections.length; i ++ ) {
|
729 |
+
|
730 |
+
if ( allIntersections[ i ].object.visible || includeInvisible ) {
|
731 |
+
|
732 |
+
return allIntersections[ i ];
|
733 |
+
|
734 |
+
}
|
735 |
+
|
736 |
+
}
|
737 |
+
|
738 |
+
return false;
|
739 |
+
|
740 |
+
}
|
741 |
+
|
742 |
+
//
|
743 |
+
|
744 |
+
// Reusable utility variables
|
745 |
+
|
746 |
+
const _tempEuler = new Euler();
|
747 |
+
const _alignVector = new Vector3( 0, 1, 0 );
|
748 |
+
const _zeroVector = new Vector3( 0, 0, 0 );
|
749 |
+
const _lookAtMatrix = new Matrix4();
|
750 |
+
const _tempQuaternion2 = new Quaternion();
|
751 |
+
const _identityQuaternion = new Quaternion();
|
752 |
+
const _dirVector = new Vector3();
|
753 |
+
const _tempMatrix = new Matrix4();
|
754 |
+
|
755 |
+
const _unitX = new Vector3( 1, 0, 0 );
|
756 |
+
const _unitY = new Vector3( 0, 1, 0 );
|
757 |
+
const _unitZ = new Vector3( 0, 0, 1 );
|
758 |
+
|
759 |
+
const _v1 = new Vector3();
|
760 |
+
const _v2 = new Vector3();
|
761 |
+
const _v3 = new Vector3();
|
762 |
+
|
763 |
+
class TransformControlsRoot extends Object3D {
|
764 |
+
|
765 |
+
constructor( controls ) {
|
766 |
+
|
767 |
+
super();
|
768 |
+
|
769 |
+
this.isTransformControlsRoot = true;
|
770 |
+
|
771 |
+
this.controls = controls;
|
772 |
+
this.visible = false;
|
773 |
+
|
774 |
+
}
|
775 |
+
|
776 |
+
// updateMatrixWorld updates key transformation variables
|
777 |
+
updateMatrixWorld( force ) {
|
778 |
+
|
779 |
+
const controls = this.controls;
|
780 |
+
|
781 |
+
if ( controls.object !== undefined ) {
|
782 |
+
|
783 |
+
controls.object.updateMatrixWorld();
|
784 |
+
|
785 |
+
if ( controls.object.parent === null ) {
|
786 |
+
|
787 |
+
console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' );
|
788 |
+
|
789 |
+
} else {
|
790 |
+
|
791 |
+
controls.object.parent.matrixWorld.decompose( controls._parentPosition, controls._parentQuaternion, controls._parentScale );
|
792 |
+
|
793 |
+
}
|
794 |
+
|
795 |
+
controls.object.matrixWorld.decompose( controls.worldPosition, controls.worldQuaternion, controls._worldScale );
|
796 |
+
|
797 |
+
controls._parentQuaternionInv.copy( controls._parentQuaternion ).invert();
|
798 |
+
controls._worldQuaternionInv.copy( controls.worldQuaternion ).invert();
|
799 |
+
|
800 |
+
}
|
801 |
+
|
802 |
+
controls.camera.updateMatrixWorld();
|
803 |
+
controls.camera.matrixWorld.decompose( controls.cameraPosition, controls.cameraQuaternion, controls._cameraScale );
|
804 |
+
|
805 |
+
if ( controls.camera.isOrthographicCamera ) {
|
806 |
+
|
807 |
+
controls.camera.getWorldDirection( controls.eye ).negate();
|
808 |
+
|
809 |
+
} else {
|
810 |
+
|
811 |
+
controls.eye.copy( controls.cameraPosition ).sub( controls.worldPosition ).normalize();
|
812 |
+
|
813 |
+
}
|
814 |
+
|
815 |
+
super.updateMatrixWorld( force );
|
816 |
+
|
817 |
+
}
|
818 |
+
|
819 |
+
dispose() {
|
820 |
+
|
821 |
+
this.traverse( function ( child ) {
|
822 |
+
|
823 |
+
if ( child.geometry ) child.geometry.dispose();
|
824 |
+
if ( child.material ) child.material.dispose();
|
825 |
+
|
826 |
+
} );
|
827 |
+
|
828 |
+
}
|
829 |
+
|
830 |
+
}
|
831 |
+
|
832 |
+
class TransformControlsGizmo extends Object3D {
|
833 |
+
|
834 |
+
constructor() {
|
835 |
+
|
836 |
+
super();
|
837 |
+
|
838 |
+
this.isTransformControlsGizmo = true;
|
839 |
+
|
840 |
+
this.type = 'TransformControlsGizmo';
|
841 |
+
|
842 |
+
// shared materials
|
843 |
+
|
844 |
+
const gizmoMaterial = new MeshBasicMaterial( {
|
845 |
+
depthTest: false,
|
846 |
+
depthWrite: false,
|
847 |
+
fog: false,
|
848 |
+
toneMapped: false,
|
849 |
+
transparent: true
|
850 |
+
} );
|
851 |
+
|
852 |
+
const gizmoLineMaterial = new LineBasicMaterial( {
|
853 |
+
depthTest: false,
|
854 |
+
depthWrite: false,
|
855 |
+
fog: false,
|
856 |
+
toneMapped: false,
|
857 |
+
transparent: true
|
858 |
+
} );
|
859 |
+
|
860 |
+
// Make unique material for each axis/color
|
861 |
+
|
862 |
+
const matInvisible = gizmoMaterial.clone();
|
863 |
+
matInvisible.opacity = 0.15;
|
864 |
+
|
865 |
+
const matHelper = gizmoLineMaterial.clone();
|
866 |
+
matHelper.opacity = 0.5;
|
867 |
+
|
868 |
+
const matRed = gizmoMaterial.clone();
|
869 |
+
matRed.color.setHex( 0xff0000 );
|
870 |
+
|
871 |
+
const matGreen = gizmoMaterial.clone();
|
872 |
+
matGreen.color.setHex( 0x00ff00 );
|
873 |
+
|
874 |
+
const matBlue = gizmoMaterial.clone();
|
875 |
+
matBlue.color.setHex( 0x0000ff );
|
876 |
+
|
877 |
+
const matRedTransparent = gizmoMaterial.clone();
|
878 |
+
matRedTransparent.color.setHex( 0xff0000 );
|
879 |
+
matRedTransparent.opacity = 0.5;
|
880 |
+
|
881 |
+
const matGreenTransparent = gizmoMaterial.clone();
|
882 |
+
matGreenTransparent.color.setHex( 0x00ff00 );
|
883 |
+
matGreenTransparent.opacity = 0.5;
|
884 |
+
|
885 |
+
const matBlueTransparent = gizmoMaterial.clone();
|
886 |
+
matBlueTransparent.color.setHex( 0x0000ff );
|
887 |
+
matBlueTransparent.opacity = 0.5;
|
888 |
+
|
889 |
+
const matWhiteTransparent = gizmoMaterial.clone();
|
890 |
+
matWhiteTransparent.opacity = 0.25;
|
891 |
+
|
892 |
+
const matYellowTransparent = gizmoMaterial.clone();
|
893 |
+
matYellowTransparent.color.setHex( 0xffff00 );
|
894 |
+
matYellowTransparent.opacity = 0.25;
|
895 |
+
|
896 |
+
const matYellow = gizmoMaterial.clone();
|
897 |
+
matYellow.color.setHex( 0xffff00 );
|
898 |
+
|
899 |
+
const matGray = gizmoMaterial.clone();
|
900 |
+
matGray.color.setHex( 0x787878 );
|
901 |
+
|
902 |
+
// reusable geometry
|
903 |
+
|
904 |
+
const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 );
|
905 |
+
arrowGeometry.translate( 0, 0.05, 0 );
|
906 |
+
|
907 |
+
const scaleHandleGeometry = new BoxGeometry( 0.08, 0.08, 0.08 );
|
908 |
+
scaleHandleGeometry.translate( 0, 0.04, 0 );
|
909 |
+
|
910 |
+
const lineGeometry = new BufferGeometry();
|
911 |
+
lineGeometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) );
|
912 |
+
|
913 |
+
const lineGeometry2 = new CylinderGeometry( 0.0075, 0.0075, 0.5, 3 );
|
914 |
+
lineGeometry2.translate( 0, 0.25, 0 );
|
915 |
+
|
916 |
+
function CircleGeometry( radius, arc ) {
|
917 |
+
|
918 |
+
const geometry = new TorusGeometry( radius, 0.0075, 3, 64, arc * Math.PI * 2 );
|
919 |
+
geometry.rotateY( Math.PI / 2 );
|
920 |
+
geometry.rotateX( Math.PI / 2 );
|
921 |
+
return geometry;
|
922 |
+
|
923 |
+
}
|
924 |
+
|
925 |
+
// Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
|
926 |
+
|
927 |
+
function TranslateHelperGeometry() {
|
928 |
+
|
929 |
+
const geometry = new BufferGeometry();
|
930 |
+
|
931 |
+
geometry.setAttribute( 'position', new Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
|
932 |
+
|
933 |
+
return geometry;
|
934 |
+
|
935 |
+
}
|
936 |
+
|
937 |
+
// Gizmo definitions - custom hierarchy definitions for setupGizmo() function
|
938 |
+
|
939 |
+
const gizmoTranslate = {
|
940 |
+
X: [
|
941 |
+
[ new Mesh( arrowGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
|
942 |
+
[ new Mesh( arrowGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]],
|
943 |
+
[ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
|
944 |
+
],
|
945 |
+
Y: [
|
946 |
+
[ new Mesh( arrowGeometry, matGreen ), [ 0, 0.5, 0 ]],
|
947 |
+
[ new Mesh( arrowGeometry, matGreen ), [ 0, - 0.5, 0 ], [ Math.PI, 0, 0 ]],
|
948 |
+
[ new Mesh( lineGeometry2, matGreen ) ]
|
949 |
+
],
|
950 |
+
Z: [
|
951 |
+
[ new Mesh( arrowGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]],
|
952 |
+
[ new Mesh( arrowGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]],
|
953 |
+
[ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]]
|
954 |
+
],
|
955 |
+
XYZ: [
|
956 |
+
[ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]]
|
957 |
+
],
|
958 |
+
XY: [
|
959 |
+
[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]]
|
960 |
+
],
|
961 |
+
YZ: [
|
962 |
+
[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
|
963 |
+
],
|
964 |
+
XZ: [
|
965 |
+
[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
|
966 |
+
]
|
967 |
+
};
|
968 |
+
|
969 |
+
const pickerTranslate = {
|
970 |
+
X: [
|
971 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
|
972 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]]
|
973 |
+
],
|
974 |
+
Y: [
|
975 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]],
|
976 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]]
|
977 |
+
],
|
978 |
+
Z: [
|
979 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]],
|
980 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]]
|
981 |
+
],
|
982 |
+
XYZ: [
|
983 |
+
[ new Mesh( new OctahedronGeometry( 0.2, 0 ), matInvisible ) ]
|
984 |
+
],
|
985 |
+
XY: [
|
986 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]]
|
987 |
+
],
|
988 |
+
YZ: [
|
989 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
|
990 |
+
],
|
991 |
+
XZ: [
|
992 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
|
993 |
+
]
|
994 |
+
};
|
995 |
+
|
996 |
+
const helperTranslate = {
|
997 |
+
START: [
|
998 |
+
[ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
|
999 |
+
],
|
1000 |
+
END: [
|
1001 |
+
[ new Mesh( new OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
|
1002 |
+
],
|
1003 |
+
DELTA: [
|
1004 |
+
[ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
|
1005 |
+
],
|
1006 |
+
X: [
|
1007 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
|
1008 |
+
],
|
1009 |
+
Y: [
|
1010 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
|
1011 |
+
],
|
1012 |
+
Z: [
|
1013 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
|
1014 |
+
]
|
1015 |
+
};
|
1016 |
+
|
1017 |
+
const gizmoRotate = {
|
1018 |
+
XYZE: [
|
1019 |
+
[ new Mesh( CircleGeometry( 0.5, 1 ), matGray ), null, [ 0, Math.PI / 2, 0 ]]
|
1020 |
+
],
|
1021 |
+
X: [
|
1022 |
+
[ new Mesh( CircleGeometry( 0.5, 0.5 ), matRed ) ]
|
1023 |
+
],
|
1024 |
+
Y: [
|
1025 |
+
[ new Mesh( CircleGeometry( 0.5, 0.5 ), matGreen ), null, [ 0, 0, - Math.PI / 2 ]]
|
1026 |
+
],
|
1027 |
+
Z: [
|
1028 |
+
[ new Mesh( CircleGeometry( 0.5, 0.5 ), matBlue ), null, [ 0, Math.PI / 2, 0 ]]
|
1029 |
+
],
|
1030 |
+
E: [
|
1031 |
+
[ new Mesh( CircleGeometry( 0.75, 1 ), matYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]]
|
1032 |
+
]
|
1033 |
+
};
|
1034 |
+
|
1035 |
+
const helperRotate = {
|
1036 |
+
AXIS: [
|
1037 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
|
1038 |
+
]
|
1039 |
+
};
|
1040 |
+
|
1041 |
+
const pickerRotate = {
|
1042 |
+
XYZE: [
|
1043 |
+
[ new Mesh( new SphereGeometry( 0.25, 10, 8 ), matInvisible ) ]
|
1044 |
+
],
|
1045 |
+
X: [
|
1046 |
+
[ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
|
1047 |
+
],
|
1048 |
+
Y: [
|
1049 |
+
[ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
|
1050 |
+
],
|
1051 |
+
Z: [
|
1052 |
+
[ new Mesh( new TorusGeometry( 0.5, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
|
1053 |
+
],
|
1054 |
+
E: [
|
1055 |
+
[ new Mesh( new TorusGeometry( 0.75, 0.1, 2, 24 ), matInvisible ) ]
|
1056 |
+
]
|
1057 |
+
};
|
1058 |
+
|
1059 |
+
const gizmoScale = {
|
1060 |
+
X: [
|
1061 |
+
[ new Mesh( scaleHandleGeometry, matRed ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
|
1062 |
+
[ new Mesh( lineGeometry2, matRed ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
|
1063 |
+
[ new Mesh( scaleHandleGeometry, matRed ), [ - 0.5, 0, 0 ], [ 0, 0, Math.PI / 2 ]],
|
1064 |
+
],
|
1065 |
+
Y: [
|
1066 |
+
[ new Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.5, 0 ]],
|
1067 |
+
[ new Mesh( lineGeometry2, matGreen ) ],
|
1068 |
+
[ new Mesh( scaleHandleGeometry, matGreen ), [ 0, - 0.5, 0 ], [ 0, 0, Math.PI ]],
|
1069 |
+
],
|
1070 |
+
Z: [
|
1071 |
+
[ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]],
|
1072 |
+
[ new Mesh( lineGeometry2, matBlue ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
|
1073 |
+
[ new Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, - 0.5 ], [ - Math.PI / 2, 0, 0 ]]
|
1074 |
+
],
|
1075 |
+
XY: [
|
1076 |
+
[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]]
|
1077 |
+
],
|
1078 |
+
YZ: [
|
1079 |
+
[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
|
1080 |
+
],
|
1081 |
+
XZ: [
|
1082 |
+
[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
|
1083 |
+
],
|
1084 |
+
XYZ: [
|
1085 |
+
[ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ],
|
1086 |
+
]
|
1087 |
+
};
|
1088 |
+
|
1089 |
+
const pickerScale = {
|
1090 |
+
X: [
|
1091 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0.3, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
|
1092 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ - 0.3, 0, 0 ], [ 0, 0, Math.PI / 2 ]]
|
1093 |
+
],
|
1094 |
+
Y: [
|
1095 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0.3, 0 ]],
|
1096 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, - 0.3, 0 ], [ 0, 0, Math.PI ]]
|
1097 |
+
],
|
1098 |
+
Z: [
|
1099 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, 0.3 ], [ Math.PI / 2, 0, 0 ]],
|
1100 |
+
[ new Mesh( new CylinderGeometry( 0.2, 0, 0.6, 4 ), matInvisible ), [ 0, 0, - 0.3 ], [ - Math.PI / 2, 0, 0 ]]
|
1101 |
+
],
|
1102 |
+
XY: [
|
1103 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0.15, 0 ]],
|
1104 |
+
],
|
1105 |
+
YZ: [
|
1106 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]],
|
1107 |
+
],
|
1108 |
+
XZ: [
|
1109 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.01 ), matInvisible ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]],
|
1110 |
+
],
|
1111 |
+
XYZ: [
|
1112 |
+
[ new Mesh( new BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 0 ]],
|
1113 |
+
]
|
1114 |
+
};
|
1115 |
+
|
1116 |
+
const helperScale = {
|
1117 |
+
X: [
|
1118 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
|
1119 |
+
],
|
1120 |
+
Y: [
|
1121 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
|
1122 |
+
],
|
1123 |
+
Z: [
|
1124 |
+
[ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
|
1125 |
+
]
|
1126 |
+
};
|
1127 |
+
|
1128 |
+
// Creates an Object3D with gizmos described in custom hierarchy definition.
|
1129 |
+
|
1130 |
+
function setupGizmo( gizmoMap ) {
|
1131 |
+
|
1132 |
+
const gizmo = new Object3D();
|
1133 |
+
|
1134 |
+
for ( const name in gizmoMap ) {
|
1135 |
+
|
1136 |
+
for ( let i = gizmoMap[ name ].length; i --; ) {
|
1137 |
+
|
1138 |
+
const object = gizmoMap[ name ][ i ][ 0 ].clone();
|
1139 |
+
const position = gizmoMap[ name ][ i ][ 1 ];
|
1140 |
+
const rotation = gizmoMap[ name ][ i ][ 2 ];
|
1141 |
+
const scale = gizmoMap[ name ][ i ][ 3 ];
|
1142 |
+
const tag = gizmoMap[ name ][ i ][ 4 ];
|
1143 |
+
|
1144 |
+
// name and tag properties are essential for picking and updating logic.
|
1145 |
+
object.name = name;
|
1146 |
+
object.tag = tag;
|
1147 |
+
|
1148 |
+
if ( position ) {
|
1149 |
+
|
1150 |
+
object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );
|
1151 |
+
|
1152 |
+
}
|
1153 |
+
|
1154 |
+
if ( rotation ) {
|
1155 |
+
|
1156 |
+
object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );
|
1157 |
+
|
1158 |
+
}
|
1159 |
+
|
1160 |
+
if ( scale ) {
|
1161 |
+
|
1162 |
+
object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] );
|
1163 |
+
|
1164 |
+
}
|
1165 |
+
|
1166 |
+
object.updateMatrix();
|
1167 |
+
|
1168 |
+
const tempGeometry = object.geometry.clone();
|
1169 |
+
tempGeometry.applyMatrix4( object.matrix );
|
1170 |
+
object.geometry = tempGeometry;
|
1171 |
+
object.renderOrder = Infinity;
|
1172 |
+
|
1173 |
+
object.position.set( 0, 0, 0 );
|
1174 |
+
object.rotation.set( 0, 0, 0 );
|
1175 |
+
object.scale.set( 1, 1, 1 );
|
1176 |
+
|
1177 |
+
gizmo.add( object );
|
1178 |
+
|
1179 |
+
}
|
1180 |
+
|
1181 |
+
}
|
1182 |
+
|
1183 |
+
return gizmo;
|
1184 |
+
|
1185 |
+
}
|
1186 |
+
|
1187 |
+
// Gizmo creation
|
1188 |
+
|
1189 |
+
this.gizmo = {};
|
1190 |
+
this.picker = {};
|
1191 |
+
this.helper = {};
|
1192 |
+
|
1193 |
+
this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) );
|
1194 |
+
this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) );
|
1195 |
+
this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) );
|
1196 |
+
this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) );
|
1197 |
+
this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) );
|
1198 |
+
this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) );
|
1199 |
+
this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) );
|
1200 |
+
this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) );
|
1201 |
+
this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) );
|
1202 |
+
|
1203 |
+
// Pickers should be hidden always
|
1204 |
+
|
1205 |
+
this.picker[ 'translate' ].visible = false;
|
1206 |
+
this.picker[ 'rotate' ].visible = false;
|
1207 |
+
this.picker[ 'scale' ].visible = false;
|
1208 |
+
|
1209 |
+
}
|
1210 |
+
|
1211 |
+
// updateMatrixWorld will update transformations and appearance of individual handles
|
1212 |
+
|
1213 |
+
updateMatrixWorld( force ) {
|
1214 |
+
|
1215 |
+
const space = ( this.mode === 'scale' ) ? 'local' : this.space; // scale always oriented to local rotation
|
1216 |
+
|
1217 |
+
const quaternion = ( space === 'local' ) ? this.worldQuaternion : _identityQuaternion;
|
1218 |
+
|
1219 |
+
// Show only gizmos for current transform mode
|
1220 |
+
|
1221 |
+
this.gizmo[ 'translate' ].visible = this.mode === 'translate';
|
1222 |
+
this.gizmo[ 'rotate' ].visible = this.mode === 'rotate';
|
1223 |
+
this.gizmo[ 'scale' ].visible = this.mode === 'scale';
|
1224 |
+
|
1225 |
+
this.helper[ 'translate' ].visible = this.mode === 'translate';
|
1226 |
+
this.helper[ 'rotate' ].visible = this.mode === 'rotate';
|
1227 |
+
this.helper[ 'scale' ].visible = this.mode === 'scale';
|
1228 |
+
|
1229 |
+
|
1230 |
+
let handles = [];
|
1231 |
+
handles = handles.concat( this.picker[ this.mode ].children );
|
1232 |
+
handles = handles.concat( this.gizmo[ this.mode ].children );
|
1233 |
+
handles = handles.concat( this.helper[ this.mode ].children );
|
1234 |
+
|
1235 |
+
for ( let i = 0; i < handles.length; i ++ ) {
|
1236 |
+
|
1237 |
+
const handle = handles[ i ];
|
1238 |
+
|
1239 |
+
// hide aligned to camera
|
1240 |
+
|
1241 |
+
handle.visible = true;
|
1242 |
+
handle.rotation.set( 0, 0, 0 );
|
1243 |
+
handle.position.copy( this.worldPosition );
|
1244 |
+
|
1245 |
+
let factor;
|
1246 |
+
|
1247 |
+
if ( this.camera.isOrthographicCamera ) {
|
1248 |
+
|
1249 |
+
factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
|
1250 |
+
|
1251 |
+
} else {
|
1252 |
+
|
1253 |
+
factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
|
1254 |
+
|
1255 |
+
}
|
1256 |
+
|
1257 |
+
handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 4 );
|
1258 |
+
|
1259 |
+
// TODO: simplify helpers and consider decoupling from gizmo
|
1260 |
+
|
1261 |
+
if ( handle.tag === 'helper' ) {
|
1262 |
+
|
1263 |
+
handle.visible = false;
|
1264 |
+
|
1265 |
+
if ( handle.name === 'AXIS' ) {
|
1266 |
+
|
1267 |
+
handle.visible = !! this.axis;
|
1268 |
+
|
1269 |
+
if ( this.axis === 'X' ) {
|
1270 |
+
|
1271 |
+
_tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, 0 ) );
|
1272 |
+
handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
|
1273 |
+
|
1274 |
+
if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
|
1275 |
+
|
1276 |
+
handle.visible = false;
|
1277 |
+
|
1278 |
+
}
|
1279 |
+
|
1280 |
+
}
|
1281 |
+
|
1282 |
+
if ( this.axis === 'Y' ) {
|
1283 |
+
|
1284 |
+
_tempQuaternion.setFromEuler( _tempEuler.set( 0, 0, Math.PI / 2 ) );
|
1285 |
+
handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
|
1286 |
+
|
1287 |
+
if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
|
1288 |
+
|
1289 |
+
handle.visible = false;
|
1290 |
+
|
1291 |
+
}
|
1292 |
+
|
1293 |
+
}
|
1294 |
+
|
1295 |
+
if ( this.axis === 'Z' ) {
|
1296 |
+
|
1297 |
+
_tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) );
|
1298 |
+
handle.quaternion.copy( quaternion ).multiply( _tempQuaternion );
|
1299 |
+
|
1300 |
+
if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
|
1301 |
+
|
1302 |
+
handle.visible = false;
|
1303 |
+
|
1304 |
+
}
|
1305 |
+
|
1306 |
+
}
|
1307 |
+
|
1308 |
+
if ( this.axis === 'XYZE' ) {
|
1309 |
+
|
1310 |
+
_tempQuaternion.setFromEuler( _tempEuler.set( 0, Math.PI / 2, 0 ) );
|
1311 |
+
_alignVector.copy( this.rotationAxis );
|
1312 |
+
handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( _zeroVector, _alignVector, _unitY ) );
|
1313 |
+
handle.quaternion.multiply( _tempQuaternion );
|
1314 |
+
handle.visible = this.dragging;
|
1315 |
+
|
1316 |
+
}
|
1317 |
+
|
1318 |
+
if ( this.axis === 'E' ) {
|
1319 |
+
|
1320 |
+
handle.visible = false;
|
1321 |
+
|
1322 |
+
}
|
1323 |
+
|
1324 |
+
|
1325 |
+
} else if ( handle.name === 'START' ) {
|
1326 |
+
|
1327 |
+
handle.position.copy( this.worldPositionStart );
|
1328 |
+
handle.visible = this.dragging;
|
1329 |
+
|
1330 |
+
} else if ( handle.name === 'END' ) {
|
1331 |
+
|
1332 |
+
handle.position.copy( this.worldPosition );
|
1333 |
+
handle.visible = this.dragging;
|
1334 |
+
|
1335 |
+
} else if ( handle.name === 'DELTA' ) {
|
1336 |
+
|
1337 |
+
handle.position.copy( this.worldPositionStart );
|
1338 |
+
handle.quaternion.copy( this.worldQuaternionStart );
|
1339 |
+
_tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 );
|
1340 |
+
_tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() );
|
1341 |
+
handle.scale.copy( _tempVector );
|
1342 |
+
handle.visible = this.dragging;
|
1343 |
+
|
1344 |
+
} else {
|
1345 |
+
|
1346 |
+
handle.quaternion.copy( quaternion );
|
1347 |
+
|
1348 |
+
if ( this.dragging ) {
|
1349 |
+
|
1350 |
+
handle.position.copy( this.worldPositionStart );
|
1351 |
+
|
1352 |
+
} else {
|
1353 |
+
|
1354 |
+
handle.position.copy( this.worldPosition );
|
1355 |
+
|
1356 |
+
}
|
1357 |
+
|
1358 |
+
if ( this.axis ) {
|
1359 |
+
|
1360 |
+
handle.visible = this.axis.search( handle.name ) !== - 1;
|
1361 |
+
|
1362 |
+
}
|
1363 |
+
|
1364 |
+
}
|
1365 |
+
|
1366 |
+
// If updating helper, skip rest of the loop
|
1367 |
+
continue;
|
1368 |
+
|
1369 |
+
}
|
1370 |
+
|
1371 |
+
// Align handles to current local or world rotation
|
1372 |
+
|
1373 |
+
handle.quaternion.copy( quaternion );
|
1374 |
+
|
1375 |
+
if ( this.mode === 'translate' || this.mode === 'scale' ) {
|
1376 |
+
|
1377 |
+
// Hide translate and scale axis facing the camera
|
1378 |
+
|
1379 |
+
const AXIS_HIDE_THRESHOLD = 0.99;
|
1380 |
+
const PLANE_HIDE_THRESHOLD = 0.2;
|
1381 |
+
|
1382 |
+
if ( handle.name === 'X' ) {
|
1383 |
+
|
1384 |
+
if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) {
|
1385 |
+
|
1386 |
+
handle.scale.set( 1e-10, 1e-10, 1e-10 );
|
1387 |
+
handle.visible = false;
|
1388 |
+
|
1389 |
+
}
|
1390 |
+
|
1391 |
+
}
|
1392 |
+
|
1393 |
+
if ( handle.name === 'Y' ) {
|
1394 |
+
|
1395 |
+
if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) {
|
1396 |
+
|
1397 |
+
handle.scale.set( 1e-10, 1e-10, 1e-10 );
|
1398 |
+
handle.visible = false;
|
1399 |
+
|
1400 |
+
}
|
1401 |
+
|
1402 |
+
}
|
1403 |
+
|
1404 |
+
if ( handle.name === 'Z' ) {
|
1405 |
+
|
1406 |
+
if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_THRESHOLD ) {
|
1407 |
+
|
1408 |
+
handle.scale.set( 1e-10, 1e-10, 1e-10 );
|
1409 |
+
handle.visible = false;
|
1410 |
+
|
1411 |
+
}
|
1412 |
+
|
1413 |
+
}
|
1414 |
+
|
1415 |
+
if ( handle.name === 'XY' ) {
|
1416 |
+
|
1417 |
+
if ( Math.abs( _alignVector.copy( _unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) {
|
1418 |
+
|
1419 |
+
handle.scale.set( 1e-10, 1e-10, 1e-10 );
|
1420 |
+
handle.visible = false;
|
1421 |
+
|
1422 |
+
}
|
1423 |
+
|
1424 |
+
}
|
1425 |
+
|
1426 |
+
if ( handle.name === 'YZ' ) {
|
1427 |
+
|
1428 |
+
if ( Math.abs( _alignVector.copy( _unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) {
|
1429 |
+
|
1430 |
+
handle.scale.set( 1e-10, 1e-10, 1e-10 );
|
1431 |
+
handle.visible = false;
|
1432 |
+
|
1433 |
+
}
|
1434 |
+
|
1435 |
+
}
|
1436 |
+
|
1437 |
+
if ( handle.name === 'XZ' ) {
|
1438 |
+
|
1439 |
+
if ( Math.abs( _alignVector.copy( _unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_THRESHOLD ) {
|
1440 |
+
|
1441 |
+
handle.scale.set( 1e-10, 1e-10, 1e-10 );
|
1442 |
+
handle.visible = false;
|
1443 |
+
|
1444 |
+
}
|
1445 |
+
|
1446 |
+
}
|
1447 |
+
|
1448 |
+
} else if ( this.mode === 'rotate' ) {
|
1449 |
+
|
1450 |
+
// Align handles to current local or world rotation
|
1451 |
+
|
1452 |
+
_tempQuaternion2.copy( quaternion );
|
1453 |
+
_alignVector.copy( this.eye ).applyQuaternion( _tempQuaternion.copy( quaternion ).invert() );
|
1454 |
+
|
1455 |
+
if ( handle.name.search( 'E' ) !== - 1 ) {
|
1456 |
+
|
1457 |
+
handle.quaternion.setFromRotationMatrix( _lookAtMatrix.lookAt( this.eye, _zeroVector, _unitY ) );
|
1458 |
+
|
1459 |
+
}
|
1460 |
+
|
1461 |
+
if ( handle.name === 'X' ) {
|
1462 |
+
|
1463 |
+
_tempQuaternion.setFromAxisAngle( _unitX, Math.atan2( - _alignVector.y, _alignVector.z ) );
|
1464 |
+
_tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
|
1465 |
+
handle.quaternion.copy( _tempQuaternion );
|
1466 |
+
|
1467 |
+
}
|
1468 |
+
|
1469 |
+
if ( handle.name === 'Y' ) {
|
1470 |
+
|
1471 |
+
_tempQuaternion.setFromAxisAngle( _unitY, Math.atan2( _alignVector.x, _alignVector.z ) );
|
1472 |
+
_tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
|
1473 |
+
handle.quaternion.copy( _tempQuaternion );
|
1474 |
+
|
1475 |
+
}
|
1476 |
+
|
1477 |
+
if ( handle.name === 'Z' ) {
|
1478 |
+
|
1479 |
+
_tempQuaternion.setFromAxisAngle( _unitZ, Math.atan2( _alignVector.y, _alignVector.x ) );
|
1480 |
+
_tempQuaternion.multiplyQuaternions( _tempQuaternion2, _tempQuaternion );
|
1481 |
+
handle.quaternion.copy( _tempQuaternion );
|
1482 |
+
|
1483 |
+
}
|
1484 |
+
|
1485 |
+
}
|
1486 |
+
|
1487 |
+
// Hide disabled axes
|
1488 |
+
handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX );
|
1489 |
+
handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY );
|
1490 |
+
handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ );
|
1491 |
+
handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) );
|
1492 |
+
|
1493 |
+
// highlight selected axis
|
1494 |
+
|
1495 |
+
handle.material._color = handle.material._color || handle.material.color.clone();
|
1496 |
+
handle.material._opacity = handle.material._opacity || handle.material.opacity;
|
1497 |
+
|
1498 |
+
handle.material.color.copy( handle.material._color );
|
1499 |
+
handle.material.opacity = handle.material._opacity;
|
1500 |
+
|
1501 |
+
if ( this.enabled && this.axis ) {
|
1502 |
+
|
1503 |
+
if ( handle.name === this.axis ) {
|
1504 |
+
|
1505 |
+
handle.material.color.setHex( 0xffff00 );
|
1506 |
+
handle.material.opacity = 1.0;
|
1507 |
+
|
1508 |
+
} else if ( this.axis.split( '' ).some( function ( a ) {
|
1509 |
+
|
1510 |
+
return handle.name === a;
|
1511 |
+
|
1512 |
+
} ) ) {
|
1513 |
+
|
1514 |
+
handle.material.color.setHex( 0xffff00 );
|
1515 |
+
handle.material.opacity = 1.0;
|
1516 |
+
|
1517 |
+
}
|
1518 |
+
|
1519 |
+
}
|
1520 |
+
|
1521 |
+
}
|
1522 |
+
|
1523 |
+
super.updateMatrixWorld( force );
|
1524 |
+
|
1525 |
+
}
|
1526 |
+
|
1527 |
+
}
|
1528 |
+
|
1529 |
+
//
|
1530 |
+
|
1531 |
+
class TransformControlsPlane extends Mesh {
|
1532 |
+
|
1533 |
+
constructor() {
|
1534 |
+
|
1535 |
+
super(
|
1536 |
+
new PlaneGeometry( 100000, 100000, 2, 2 ),
|
1537 |
+
new MeshBasicMaterial( { visible: false, wireframe: true, side: DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } )
|
1538 |
+
);
|
1539 |
+
|
1540 |
+
this.isTransformControlsPlane = true;
|
1541 |
+
|
1542 |
+
this.type = 'TransformControlsPlane';
|
1543 |
+
|
1544 |
+
}
|
1545 |
+
|
1546 |
+
updateMatrixWorld( force ) {
|
1547 |
+
|
1548 |
+
let space = this.space;
|
1549 |
+
|
1550 |
+
this.position.copy( this.worldPosition );
|
1551 |
+
|
1552 |
+
if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
|
1553 |
+
|
1554 |
+
_v1.copy( _unitX ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
|
1555 |
+
_v2.copy( _unitY ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
|
1556 |
+
_v3.copy( _unitZ ).applyQuaternion( space === 'local' ? this.worldQuaternion : _identityQuaternion );
|
1557 |
+
|
1558 |
+
// Align the plane for current transform mode, axis and space.
|
1559 |
+
|
1560 |
+
_alignVector.copy( _v2 );
|
1561 |
+
|
1562 |
+
switch ( this.mode ) {
|
1563 |
+
|
1564 |
+
case 'translate':
|
1565 |
+
case 'scale':
|
1566 |
+
switch ( this.axis ) {
|
1567 |
+
|
1568 |
+
case 'X':
|
1569 |
+
_alignVector.copy( this.eye ).cross( _v1 );
|
1570 |
+
_dirVector.copy( _v1 ).cross( _alignVector );
|
1571 |
+
break;
|
1572 |
+
case 'Y':
|
1573 |
+
_alignVector.copy( this.eye ).cross( _v2 );
|
1574 |
+
_dirVector.copy( _v2 ).cross( _alignVector );
|
1575 |
+
break;
|
1576 |
+
case 'Z':
|
1577 |
+
_alignVector.copy( this.eye ).cross( _v3 );
|
1578 |
+
_dirVector.copy( _v3 ).cross( _alignVector );
|
1579 |
+
break;
|
1580 |
+
case 'XY':
|
1581 |
+
_dirVector.copy( _v3 );
|
1582 |
+
break;
|
1583 |
+
case 'YZ':
|
1584 |
+
_dirVector.copy( _v1 );
|
1585 |
+
break;
|
1586 |
+
case 'XZ':
|
1587 |
+
_alignVector.copy( _v3 );
|
1588 |
+
_dirVector.copy( _v2 );
|
1589 |
+
break;
|
1590 |
+
case 'XYZ':
|
1591 |
+
case 'E':
|
1592 |
+
_dirVector.set( 0, 0, 0 );
|
1593 |
+
break;
|
1594 |
+
|
1595 |
+
}
|
1596 |
+
|
1597 |
+
break;
|
1598 |
+
case 'rotate':
|
1599 |
+
default:
|
1600 |
+
// special case for rotate
|
1601 |
+
_dirVector.set( 0, 0, 0 );
|
1602 |
+
|
1603 |
+
}
|
1604 |
+
|
1605 |
+
if ( _dirVector.length() === 0 ) {
|
1606 |
+
|
1607 |
+
// If in rotate mode, make the plane parallel to camera
|
1608 |
+
this.quaternion.copy( this.cameraQuaternion );
|
1609 |
+
|
1610 |
+
} else {
|
1611 |
+
|
1612 |
+
_tempMatrix.lookAt( _tempVector.set( 0, 0, 0 ), _dirVector, _alignVector );
|
1613 |
+
|
1614 |
+
this.quaternion.setFromRotationMatrix( _tempMatrix );
|
1615 |
+
|
1616 |
+
}
|
1617 |
+
|
1618 |
+
super.updateMatrixWorld( force );
|
1619 |
+
|
1620 |
+
}
|
1621 |
+
|
1622 |
+
}
|
1623 |
+
|
1624 |
+
export { TransformControls, TransformControlsGizmo, TransformControlsPlane };
|
libs/three.js/0.172.0/jsm/csm/CSM.js
ADDED
@@ -0,0 +1,384 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Vector2,
|
3 |
+
Vector3,
|
4 |
+
DirectionalLight,
|
5 |
+
MathUtils,
|
6 |
+
ShaderChunk,
|
7 |
+
Matrix4,
|
8 |
+
Box3
|
9 |
+
} from 'three';
|
10 |
+
import { CSMFrustum } from './CSMFrustum.js';
|
11 |
+
import { CSMShader } from './CSMShader.js';
|
12 |
+
|
13 |
+
const _cameraToLightMatrix = new Matrix4();
|
14 |
+
const _lightSpaceFrustum = new CSMFrustum( { webGL: true } );
|
15 |
+
const _center = new Vector3();
|
16 |
+
const _bbox = new Box3();
|
17 |
+
const _uniformArray = [];
|
18 |
+
const _logArray = [];
|
19 |
+
const _lightOrientationMatrix = new Matrix4();
|
20 |
+
const _lightOrientationMatrixInverse = new Matrix4();
|
21 |
+
const _up = new Vector3( 0, 1, 0 );
|
22 |
+
|
23 |
+
export class CSM {
|
24 |
+
|
25 |
+
constructor( data ) {
|
26 |
+
|
27 |
+
this.camera = data.camera;
|
28 |
+
this.parent = data.parent;
|
29 |
+
this.cascades = data.cascades || 3;
|
30 |
+
this.maxFar = data.maxFar || 100000;
|
31 |
+
this.mode = data.mode || 'practical';
|
32 |
+
this.shadowMapSize = data.shadowMapSize || 2048;
|
33 |
+
this.shadowBias = data.shadowBias || 0.000001;
|
34 |
+
this.lightDirection = data.lightDirection || new Vector3( 1, - 1, 1 ).normalize();
|
35 |
+
this.lightIntensity = data.lightIntensity || 3;
|
36 |
+
this.lightNear = data.lightNear || 1;
|
37 |
+
this.lightFar = data.lightFar || 2000;
|
38 |
+
this.lightMargin = data.lightMargin || 200;
|
39 |
+
this.customSplitsCallback = data.customSplitsCallback;
|
40 |
+
this.fade = false;
|
41 |
+
this.mainFrustum = new CSMFrustum( { webGL: true } );
|
42 |
+
this.frustums = [];
|
43 |
+
this.breaks = [];
|
44 |
+
|
45 |
+
this.lights = [];
|
46 |
+
this.shaders = new Map();
|
47 |
+
|
48 |
+
this.createLights();
|
49 |
+
this.updateFrustums();
|
50 |
+
this.injectInclude();
|
51 |
+
|
52 |
+
}
|
53 |
+
|
54 |
+
createLights() {
|
55 |
+
|
56 |
+
for ( let i = 0; i < this.cascades; i ++ ) {
|
57 |
+
|
58 |
+
const light = new DirectionalLight( 0xffffff, this.lightIntensity );
|
59 |
+
light.castShadow = true;
|
60 |
+
light.shadow.mapSize.width = this.shadowMapSize;
|
61 |
+
light.shadow.mapSize.height = this.shadowMapSize;
|
62 |
+
|
63 |
+
light.shadow.camera.near = this.lightNear;
|
64 |
+
light.shadow.camera.far = this.lightFar;
|
65 |
+
light.shadow.bias = this.shadowBias;
|
66 |
+
|
67 |
+
this.parent.add( light );
|
68 |
+
this.parent.add( light.target );
|
69 |
+
this.lights.push( light );
|
70 |
+
|
71 |
+
}
|
72 |
+
|
73 |
+
}
|
74 |
+
|
75 |
+
initCascades() {
|
76 |
+
|
77 |
+
const camera = this.camera;
|
78 |
+
camera.updateProjectionMatrix();
|
79 |
+
this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar );
|
80 |
+
this.mainFrustum.split( this.breaks, this.frustums );
|
81 |
+
|
82 |
+
}
|
83 |
+
|
84 |
+
updateShadowBounds() {
|
85 |
+
|
86 |
+
const frustums = this.frustums;
|
87 |
+
for ( let i = 0; i < frustums.length; i ++ ) {
|
88 |
+
|
89 |
+
const light = this.lights[ i ];
|
90 |
+
const shadowCam = light.shadow.camera;
|
91 |
+
const frustum = this.frustums[ i ];
|
92 |
+
|
93 |
+
// Get the two points that represent that furthest points on the frustum assuming
|
94 |
+
// that's either the diagonal across the far plane or the diagonal across the whole
|
95 |
+
// frustum itself.
|
96 |
+
const nearVerts = frustum.vertices.near;
|
97 |
+
const farVerts = frustum.vertices.far;
|
98 |
+
const point1 = farVerts[ 0 ];
|
99 |
+
let point2;
|
100 |
+
if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) {
|
101 |
+
|
102 |
+
point2 = farVerts[ 2 ];
|
103 |
+
|
104 |
+
} else {
|
105 |
+
|
106 |
+
point2 = nearVerts[ 2 ];
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
let squaredBBWidth = point1.distanceTo( point2 );
|
111 |
+
if ( this.fade ) {
|
112 |
+
|
113 |
+
// expand the shadow extents by the fade margin if fade is enabled.
|
114 |
+
const camera = this.camera;
|
115 |
+
const far = Math.max( camera.far, this.maxFar );
|
116 |
+
const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near );
|
117 |
+
const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near );
|
118 |
+
|
119 |
+
squaredBBWidth += margin;
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
shadowCam.left = - squaredBBWidth / 2;
|
124 |
+
shadowCam.right = squaredBBWidth / 2;
|
125 |
+
shadowCam.top = squaredBBWidth / 2;
|
126 |
+
shadowCam.bottom = - squaredBBWidth / 2;
|
127 |
+
shadowCam.updateProjectionMatrix();
|
128 |
+
|
129 |
+
}
|
130 |
+
|
131 |
+
}
|
132 |
+
|
133 |
+
getBreaks() {
|
134 |
+
|
135 |
+
const camera = this.camera;
|
136 |
+
const far = Math.min( camera.far, this.maxFar );
|
137 |
+
this.breaks.length = 0;
|
138 |
+
|
139 |
+
switch ( this.mode ) {
|
140 |
+
|
141 |
+
case 'uniform':
|
142 |
+
uniformSplit( this.cascades, camera.near, far, this.breaks );
|
143 |
+
break;
|
144 |
+
case 'logarithmic':
|
145 |
+
logarithmicSplit( this.cascades, camera.near, far, this.breaks );
|
146 |
+
break;
|
147 |
+
case 'practical':
|
148 |
+
practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks );
|
149 |
+
break;
|
150 |
+
case 'custom':
|
151 |
+
if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
|
152 |
+
this.customSplitsCallback( this.cascades, camera.near, far, this.breaks );
|
153 |
+
break;
|
154 |
+
|
155 |
+
}
|
156 |
+
|
157 |
+
function uniformSplit( amount, near, far, target ) {
|
158 |
+
|
159 |
+
for ( let i = 1; i < amount; i ++ ) {
|
160 |
+
|
161 |
+
target.push( ( near + ( far - near ) * i / amount ) / far );
|
162 |
+
|
163 |
+
}
|
164 |
+
|
165 |
+
target.push( 1 );
|
166 |
+
|
167 |
+
}
|
168 |
+
|
169 |
+
function logarithmicSplit( amount, near, far, target ) {
|
170 |
+
|
171 |
+
for ( let i = 1; i < amount; i ++ ) {
|
172 |
+
|
173 |
+
target.push( ( near * ( far / near ) ** ( i / amount ) ) / far );
|
174 |
+
|
175 |
+
}
|
176 |
+
|
177 |
+
target.push( 1 );
|
178 |
+
|
179 |
+
}
|
180 |
+
|
181 |
+
function practicalSplit( amount, near, far, lambda, target ) {
|
182 |
+
|
183 |
+
_uniformArray.length = 0;
|
184 |
+
_logArray.length = 0;
|
185 |
+
logarithmicSplit( amount, near, far, _logArray );
|
186 |
+
uniformSplit( amount, near, far, _uniformArray );
|
187 |
+
|
188 |
+
for ( let i = 1; i < amount; i ++ ) {
|
189 |
+
|
190 |
+
target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) );
|
191 |
+
|
192 |
+
}
|
193 |
+
|
194 |
+
target.push( 1 );
|
195 |
+
|
196 |
+
}
|
197 |
+
|
198 |
+
}
|
199 |
+
|
200 |
+
update() {
|
201 |
+
|
202 |
+
const camera = this.camera;
|
203 |
+
const frustums = this.frustums;
|
204 |
+
|
205 |
+
// for each frustum we need to find its min-max box aligned with the light orientation
|
206 |
+
// the position in _lightOrientationMatrix does not matter, as we transform there and back
|
207 |
+
_lightOrientationMatrix.lookAt( new Vector3(), this.lightDirection, _up );
|
208 |
+
_lightOrientationMatrixInverse.copy( _lightOrientationMatrix ).invert();
|
209 |
+
|
210 |
+
for ( let i = 0; i < frustums.length; i ++ ) {
|
211 |
+
|
212 |
+
const light = this.lights[ i ];
|
213 |
+
const shadowCam = light.shadow.camera;
|
214 |
+
const texelWidth = ( shadowCam.right - shadowCam.left ) / this.shadowMapSize;
|
215 |
+
const texelHeight = ( shadowCam.top - shadowCam.bottom ) / this.shadowMapSize;
|
216 |
+
_cameraToLightMatrix.multiplyMatrices( _lightOrientationMatrixInverse, camera.matrixWorld );
|
217 |
+
frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum );
|
218 |
+
|
219 |
+
const nearVerts = _lightSpaceFrustum.vertices.near;
|
220 |
+
const farVerts = _lightSpaceFrustum.vertices.far;
|
221 |
+
_bbox.makeEmpty();
|
222 |
+
for ( let j = 0; j < 4; j ++ ) {
|
223 |
+
|
224 |
+
_bbox.expandByPoint( nearVerts[ j ] );
|
225 |
+
_bbox.expandByPoint( farVerts[ j ] );
|
226 |
+
|
227 |
+
}
|
228 |
+
|
229 |
+
_bbox.getCenter( _center );
|
230 |
+
_center.z = _bbox.max.z + this.lightMargin;
|
231 |
+
_center.x = Math.floor( _center.x / texelWidth ) * texelWidth;
|
232 |
+
_center.y = Math.floor( _center.y / texelHeight ) * texelHeight;
|
233 |
+
_center.applyMatrix4( _lightOrientationMatrix );
|
234 |
+
|
235 |
+
light.position.copy( _center );
|
236 |
+
light.target.position.copy( _center );
|
237 |
+
|
238 |
+
light.target.position.x += this.lightDirection.x;
|
239 |
+
light.target.position.y += this.lightDirection.y;
|
240 |
+
light.target.position.z += this.lightDirection.z;
|
241 |
+
|
242 |
+
}
|
243 |
+
|
244 |
+
}
|
245 |
+
|
246 |
+
injectInclude() {
|
247 |
+
|
248 |
+
ShaderChunk.lights_fragment_begin = CSMShader.lights_fragment_begin;
|
249 |
+
ShaderChunk.lights_pars_begin = CSMShader.lights_pars_begin;
|
250 |
+
|
251 |
+
}
|
252 |
+
|
253 |
+
setupMaterial( material ) {
|
254 |
+
|
255 |
+
material.defines = material.defines || {};
|
256 |
+
material.defines.USE_CSM = 1;
|
257 |
+
material.defines.CSM_CASCADES = this.cascades;
|
258 |
+
|
259 |
+
if ( this.fade ) {
|
260 |
+
|
261 |
+
material.defines.CSM_FADE = '';
|
262 |
+
|
263 |
+
}
|
264 |
+
|
265 |
+
const breaksVec2 = [];
|
266 |
+
const scope = this;
|
267 |
+
const shaders = this.shaders;
|
268 |
+
|
269 |
+
material.onBeforeCompile = function ( shader ) {
|
270 |
+
|
271 |
+
const far = Math.min( scope.camera.far, scope.maxFar );
|
272 |
+
scope.getExtendedBreaks( breaksVec2 );
|
273 |
+
|
274 |
+
shader.uniforms.CSM_cascades = { value: breaksVec2 };
|
275 |
+
shader.uniforms.cameraNear = { value: scope.camera.near };
|
276 |
+
shader.uniforms.shadowFar = { value: far };
|
277 |
+
|
278 |
+
shaders.set( material, shader );
|
279 |
+
|
280 |
+
};
|
281 |
+
|
282 |
+
shaders.set( material, null );
|
283 |
+
|
284 |
+
}
|
285 |
+
|
286 |
+
updateUniforms() {
|
287 |
+
|
288 |
+
const far = Math.min( this.camera.far, this.maxFar );
|
289 |
+
const shaders = this.shaders;
|
290 |
+
|
291 |
+
shaders.forEach( function ( shader, material ) {
|
292 |
+
|
293 |
+
if ( shader !== null ) {
|
294 |
+
|
295 |
+
const uniforms = shader.uniforms;
|
296 |
+
this.getExtendedBreaks( uniforms.CSM_cascades.value );
|
297 |
+
uniforms.cameraNear.value = this.camera.near;
|
298 |
+
uniforms.shadowFar.value = far;
|
299 |
+
|
300 |
+
}
|
301 |
+
|
302 |
+
if ( ! this.fade && 'CSM_FADE' in material.defines ) {
|
303 |
+
|
304 |
+
delete material.defines.CSM_FADE;
|
305 |
+
material.needsUpdate = true;
|
306 |
+
|
307 |
+
} else if ( this.fade && ! ( 'CSM_FADE' in material.defines ) ) {
|
308 |
+
|
309 |
+
material.defines.CSM_FADE = '';
|
310 |
+
material.needsUpdate = true;
|
311 |
+
|
312 |
+
}
|
313 |
+
|
314 |
+
}, this );
|
315 |
+
|
316 |
+
}
|
317 |
+
|
318 |
+
getExtendedBreaks( target ) {
|
319 |
+
|
320 |
+
while ( target.length < this.breaks.length ) {
|
321 |
+
|
322 |
+
target.push( new Vector2() );
|
323 |
+
|
324 |
+
}
|
325 |
+
|
326 |
+
target.length = this.breaks.length;
|
327 |
+
|
328 |
+
for ( let i = 0; i < this.cascades; i ++ ) {
|
329 |
+
|
330 |
+
const amount = this.breaks[ i ];
|
331 |
+
const prev = this.breaks[ i - 1 ] || 0;
|
332 |
+
target[ i ].x = prev;
|
333 |
+
target[ i ].y = amount;
|
334 |
+
|
335 |
+
}
|
336 |
+
|
337 |
+
}
|
338 |
+
|
339 |
+
updateFrustums() {
|
340 |
+
|
341 |
+
this.getBreaks();
|
342 |
+
this.initCascades();
|
343 |
+
this.updateShadowBounds();
|
344 |
+
this.updateUniforms();
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
remove() {
|
349 |
+
|
350 |
+
for ( let i = 0; i < this.lights.length; i ++ ) {
|
351 |
+
|
352 |
+
this.parent.remove( this.lights[ i ].target );
|
353 |
+
this.parent.remove( this.lights[ i ] );
|
354 |
+
|
355 |
+
}
|
356 |
+
|
357 |
+
}
|
358 |
+
|
359 |
+
dispose() {
|
360 |
+
|
361 |
+
const shaders = this.shaders;
|
362 |
+
shaders.forEach( function ( shader, material ) {
|
363 |
+
|
364 |
+
delete material.onBeforeCompile;
|
365 |
+
delete material.defines.USE_CSM;
|
366 |
+
delete material.defines.CSM_CASCADES;
|
367 |
+
delete material.defines.CSM_FADE;
|
368 |
+
|
369 |
+
if ( shader !== null ) {
|
370 |
+
|
371 |
+
delete shader.uniforms.CSM_cascades;
|
372 |
+
delete shader.uniforms.cameraNear;
|
373 |
+
delete shader.uniforms.shadowFar;
|
374 |
+
|
375 |
+
}
|
376 |
+
|
377 |
+
material.needsUpdate = true;
|
378 |
+
|
379 |
+
} );
|
380 |
+
shaders.clear();
|
381 |
+
|
382 |
+
}
|
383 |
+
|
384 |
+
}
|
libs/three.js/0.172.0/jsm/csm/CSMFrustum.js
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Vector3, Matrix4 } from 'three';
|
2 |
+
|
3 |
+
const inverseProjectionMatrix = new Matrix4();
|
4 |
+
|
5 |
+
class CSMFrustum {
|
6 |
+
|
7 |
+
constructor( data ) {
|
8 |
+
|
9 |
+
data = data || {};
|
10 |
+
|
11 |
+
this.zNear = data.webGL === true ? - 1 : 0;
|
12 |
+
|
13 |
+
this.vertices = {
|
14 |
+
near: [
|
15 |
+
new Vector3(),
|
16 |
+
new Vector3(),
|
17 |
+
new Vector3(),
|
18 |
+
new Vector3()
|
19 |
+
],
|
20 |
+
far: [
|
21 |
+
new Vector3(),
|
22 |
+
new Vector3(),
|
23 |
+
new Vector3(),
|
24 |
+
new Vector3()
|
25 |
+
]
|
26 |
+
};
|
27 |
+
|
28 |
+
if ( data.projectionMatrix !== undefined ) {
|
29 |
+
|
30 |
+
this.setFromProjectionMatrix( data.projectionMatrix, data.maxFar || 10000 );
|
31 |
+
|
32 |
+
}
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
setFromProjectionMatrix( projectionMatrix, maxFar ) {
|
37 |
+
|
38 |
+
const zNear = this.zNear;
|
39 |
+
const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0;
|
40 |
+
|
41 |
+
inverseProjectionMatrix.copy( projectionMatrix ).invert();
|
42 |
+
|
43 |
+
// 3 --- 0 vertices.near/far order
|
44 |
+
// | |
|
45 |
+
// 2 --- 1
|
46 |
+
// clip space spans from [-1, 1]
|
47 |
+
|
48 |
+
this.vertices.near[ 0 ].set( 1, 1, zNear );
|
49 |
+
this.vertices.near[ 1 ].set( 1, - 1, zNear );
|
50 |
+
this.vertices.near[ 2 ].set( - 1, - 1, zNear );
|
51 |
+
this.vertices.near[ 3 ].set( - 1, 1, zNear );
|
52 |
+
this.vertices.near.forEach( function ( v ) {
|
53 |
+
|
54 |
+
v.applyMatrix4( inverseProjectionMatrix );
|
55 |
+
|
56 |
+
} );
|
57 |
+
|
58 |
+
this.vertices.far[ 0 ].set( 1, 1, 1 );
|
59 |
+
this.vertices.far[ 1 ].set( 1, - 1, 1 );
|
60 |
+
this.vertices.far[ 2 ].set( - 1, - 1, 1 );
|
61 |
+
this.vertices.far[ 3 ].set( - 1, 1, 1 );
|
62 |
+
this.vertices.far.forEach( function ( v ) {
|
63 |
+
|
64 |
+
v.applyMatrix4( inverseProjectionMatrix );
|
65 |
+
|
66 |
+
const absZ = Math.abs( v.z );
|
67 |
+
if ( isOrthographic ) {
|
68 |
+
|
69 |
+
v.z *= Math.min( maxFar / absZ, 1.0 );
|
70 |
+
|
71 |
+
} else {
|
72 |
+
|
73 |
+
v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) );
|
74 |
+
|
75 |
+
}
|
76 |
+
|
77 |
+
} );
|
78 |
+
|
79 |
+
return this.vertices;
|
80 |
+
|
81 |
+
}
|
82 |
+
|
83 |
+
split( breaks, target ) {
|
84 |
+
|
85 |
+
while ( breaks.length > target.length ) {
|
86 |
+
|
87 |
+
target.push( new CSMFrustum() );
|
88 |
+
|
89 |
+
}
|
90 |
+
|
91 |
+
target.length = breaks.length;
|
92 |
+
|
93 |
+
for ( let i = 0; i < breaks.length; i ++ ) {
|
94 |
+
|
95 |
+
const cascade = target[ i ];
|
96 |
+
|
97 |
+
if ( i === 0 ) {
|
98 |
+
|
99 |
+
for ( let j = 0; j < 4; j ++ ) {
|
100 |
+
|
101 |
+
cascade.vertices.near[ j ].copy( this.vertices.near[ j ] );
|
102 |
+
|
103 |
+
}
|
104 |
+
|
105 |
+
} else {
|
106 |
+
|
107 |
+
for ( let j = 0; j < 4; j ++ ) {
|
108 |
+
|
109 |
+
cascade.vertices.near[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] );
|
110 |
+
|
111 |
+
}
|
112 |
+
|
113 |
+
}
|
114 |
+
|
115 |
+
if ( i === breaks.length - 1 ) {
|
116 |
+
|
117 |
+
for ( let j = 0; j < 4; j ++ ) {
|
118 |
+
|
119 |
+
cascade.vertices.far[ j ].copy( this.vertices.far[ j ] );
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
} else {
|
124 |
+
|
125 |
+
for ( let j = 0; j < 4; j ++ ) {
|
126 |
+
|
127 |
+
cascade.vertices.far[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] );
|
128 |
+
|
129 |
+
}
|
130 |
+
|
131 |
+
}
|
132 |
+
|
133 |
+
}
|
134 |
+
|
135 |
+
}
|
136 |
+
|
137 |
+
toSpace( cameraMatrix, target ) {
|
138 |
+
|
139 |
+
for ( let i = 0; i < 4; i ++ ) {
|
140 |
+
|
141 |
+
target.vertices.near[ i ]
|
142 |
+
.copy( this.vertices.near[ i ] )
|
143 |
+
.applyMatrix4( cameraMatrix );
|
144 |
+
|
145 |
+
target.vertices.far[ i ]
|
146 |
+
.copy( this.vertices.far[ i ] )
|
147 |
+
.applyMatrix4( cameraMatrix );
|
148 |
+
|
149 |
+
}
|
150 |
+
|
151 |
+
}
|
152 |
+
|
153 |
+
}
|
154 |
+
|
155 |
+
export { CSMFrustum };
|
libs/three.js/0.172.0/jsm/csm/CSMHelper.js
ADDED
@@ -0,0 +1,195 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Group,
|
3 |
+
Mesh,
|
4 |
+
LineSegments,
|
5 |
+
BufferGeometry,
|
6 |
+
LineBasicMaterial,
|
7 |
+
Box3Helper,
|
8 |
+
Box3,
|
9 |
+
PlaneGeometry,
|
10 |
+
MeshBasicMaterial,
|
11 |
+
BufferAttribute,
|
12 |
+
DoubleSide
|
13 |
+
} from 'three';
|
14 |
+
|
15 |
+
class CSMHelper extends Group {
|
16 |
+
|
17 |
+
constructor( csm ) {
|
18 |
+
|
19 |
+
super();
|
20 |
+
this.csm = csm;
|
21 |
+
this.displayFrustum = true;
|
22 |
+
this.displayPlanes = true;
|
23 |
+
this.displayShadowBounds = true;
|
24 |
+
|
25 |
+
const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );
|
26 |
+
const positions = new Float32Array( 24 );
|
27 |
+
const frustumGeometry = new BufferGeometry();
|
28 |
+
frustumGeometry.setIndex( new BufferAttribute( indices, 1 ) );
|
29 |
+
frustumGeometry.setAttribute( 'position', new BufferAttribute( positions, 3, false ) );
|
30 |
+
const frustumLines = new LineSegments( frustumGeometry, new LineBasicMaterial() );
|
31 |
+
this.add( frustumLines );
|
32 |
+
|
33 |
+
this.frustumLines = frustumLines;
|
34 |
+
this.cascadeLines = [];
|
35 |
+
this.cascadePlanes = [];
|
36 |
+
this.shadowLines = [];
|
37 |
+
|
38 |
+
}
|
39 |
+
|
40 |
+
updateVisibility() {
|
41 |
+
|
42 |
+
const displayFrustum = this.displayFrustum;
|
43 |
+
const displayPlanes = this.displayPlanes;
|
44 |
+
const displayShadowBounds = this.displayShadowBounds;
|
45 |
+
|
46 |
+
const frustumLines = this.frustumLines;
|
47 |
+
const cascadeLines = this.cascadeLines;
|
48 |
+
const cascadePlanes = this.cascadePlanes;
|
49 |
+
const shadowLines = this.shadowLines;
|
50 |
+
for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) {
|
51 |
+
|
52 |
+
const cascadeLine = cascadeLines[ i ];
|
53 |
+
const cascadePlane = cascadePlanes[ i ];
|
54 |
+
const shadowLineGroup = shadowLines[ i ];
|
55 |
+
|
56 |
+
cascadeLine.visible = displayFrustum;
|
57 |
+
cascadePlane.visible = displayFrustum && displayPlanes;
|
58 |
+
shadowLineGroup.visible = displayShadowBounds;
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
frustumLines.visible = displayFrustum;
|
63 |
+
|
64 |
+
}
|
65 |
+
|
66 |
+
update() {
|
67 |
+
|
68 |
+
const csm = this.csm;
|
69 |
+
const camera = csm.camera;
|
70 |
+
const cascades = csm.cascades;
|
71 |
+
const mainFrustum = csm.mainFrustum;
|
72 |
+
const frustums = csm.frustums;
|
73 |
+
const lights = csm.lights;
|
74 |
+
|
75 |
+
const frustumLines = this.frustumLines;
|
76 |
+
const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' );
|
77 |
+
const cascadeLines = this.cascadeLines;
|
78 |
+
const cascadePlanes = this.cascadePlanes;
|
79 |
+
const shadowLines = this.shadowLines;
|
80 |
+
|
81 |
+
if ( camera === null ) return;
|
82 |
+
|
83 |
+
this.position.copy( camera.position );
|
84 |
+
this.quaternion.copy( camera.quaternion );
|
85 |
+
this.scale.copy( camera.scale );
|
86 |
+
this.updateMatrixWorld( true );
|
87 |
+
|
88 |
+
while ( cascadeLines.length > cascades ) {
|
89 |
+
|
90 |
+
this.remove( cascadeLines.pop() );
|
91 |
+
this.remove( cascadePlanes.pop() );
|
92 |
+
this.remove( shadowLines.pop() );
|
93 |
+
|
94 |
+
}
|
95 |
+
|
96 |
+
while ( cascadeLines.length < cascades ) {
|
97 |
+
|
98 |
+
const cascadeLine = new Box3Helper( new Box3(), 0xffffff );
|
99 |
+
const planeMat = new MeshBasicMaterial( { transparent: true, opacity: 0.1, depthWrite: false, side: DoubleSide } );
|
100 |
+
const cascadePlane = new Mesh( new PlaneGeometry(), planeMat );
|
101 |
+
const shadowLineGroup = new Group();
|
102 |
+
const shadowLine = new Box3Helper( new Box3(), 0xffff00 );
|
103 |
+
shadowLineGroup.add( shadowLine );
|
104 |
+
|
105 |
+
this.add( cascadeLine );
|
106 |
+
this.add( cascadePlane );
|
107 |
+
this.add( shadowLineGroup );
|
108 |
+
|
109 |
+
cascadeLines.push( cascadeLine );
|
110 |
+
cascadePlanes.push( cascadePlane );
|
111 |
+
shadowLines.push( shadowLineGroup );
|
112 |
+
|
113 |
+
}
|
114 |
+
|
115 |
+
for ( let i = 0; i < cascades; i ++ ) {
|
116 |
+
|
117 |
+
const frustum = frustums[ i ];
|
118 |
+
const light = lights[ i ];
|
119 |
+
const shadowCam = light.shadow.camera;
|
120 |
+
const farVerts = frustum.vertices.far;
|
121 |
+
|
122 |
+
const cascadeLine = cascadeLines[ i ];
|
123 |
+
const cascadePlane = cascadePlanes[ i ];
|
124 |
+
const shadowLineGroup = shadowLines[ i ];
|
125 |
+
const shadowLine = shadowLineGroup.children[ 0 ];
|
126 |
+
|
127 |
+
cascadeLine.box.min.copy( farVerts[ 2 ] );
|
128 |
+
cascadeLine.box.max.copy( farVerts[ 0 ] );
|
129 |
+
cascadeLine.box.max.z += 1e-4;
|
130 |
+
|
131 |
+
cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] );
|
132 |
+
cascadePlane.position.multiplyScalar( 0.5 );
|
133 |
+
cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] );
|
134 |
+
cascadePlane.scale.z = 1e-4;
|
135 |
+
|
136 |
+
this.remove( shadowLineGroup );
|
137 |
+
shadowLineGroup.position.copy( shadowCam.position );
|
138 |
+
shadowLineGroup.quaternion.copy( shadowCam.quaternion );
|
139 |
+
shadowLineGroup.scale.copy( shadowCam.scale );
|
140 |
+
shadowLineGroup.updateMatrixWorld( true );
|
141 |
+
this.attach( shadowLineGroup );
|
142 |
+
|
143 |
+
shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far );
|
144 |
+
shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near );
|
145 |
+
|
146 |
+
}
|
147 |
+
|
148 |
+
const nearVerts = mainFrustum.vertices.near;
|
149 |
+
const farVerts = mainFrustum.vertices.far;
|
150 |
+
frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z );
|
151 |
+
frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z );
|
152 |
+
frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z );
|
153 |
+
frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z );
|
154 |
+
|
155 |
+
frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z );
|
156 |
+
frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z );
|
157 |
+
frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z );
|
158 |
+
frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z );
|
159 |
+
frustumLinePositions.needsUpdate = true;
|
160 |
+
|
161 |
+
}
|
162 |
+
|
163 |
+
dispose() {
|
164 |
+
|
165 |
+
const frustumLines = this.frustumLines;
|
166 |
+
const cascadeLines = this.cascadeLines;
|
167 |
+
const cascadePlanes = this.cascadePlanes;
|
168 |
+
const shadowLines = this.shadowLines;
|
169 |
+
|
170 |
+
frustumLines.geometry.dispose();
|
171 |
+
frustumLines.material.dispose();
|
172 |
+
|
173 |
+
const cascades = this.csm.cascades;
|
174 |
+
|
175 |
+
for ( let i = 0; i < cascades; i ++ ) {
|
176 |
+
|
177 |
+
const cascadeLine = cascadeLines[ i ];
|
178 |
+
const cascadePlane = cascadePlanes[ i ];
|
179 |
+
const shadowLineGroup = shadowLines[ i ];
|
180 |
+
const shadowLine = shadowLineGroup.children[ 0 ];
|
181 |
+
|
182 |
+
cascadeLine.dispose(); // Box3Helper
|
183 |
+
|
184 |
+
cascadePlane.geometry.dispose();
|
185 |
+
cascadePlane.material.dispose();
|
186 |
+
|
187 |
+
shadowLine.dispose(); // Box3Helper
|
188 |
+
|
189 |
+
}
|
190 |
+
|
191 |
+
}
|
192 |
+
|
193 |
+
}
|
194 |
+
|
195 |
+
export { CSMHelper };
|
libs/three.js/0.172.0/jsm/csm/CSMShader.js
ADDED
@@ -0,0 +1,295 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { ShaderChunk } from 'three';
|
2 |
+
|
3 |
+
const CSMShader = {
|
4 |
+
lights_fragment_begin: /* glsl */`
|
5 |
+
vec3 geometryPosition = - vViewPosition;
|
6 |
+
vec3 geometryNormal = normal;
|
7 |
+
vec3 geometryViewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );
|
8 |
+
|
9 |
+
vec3 geometryClearcoatNormal = vec3( 0.0 );
|
10 |
+
|
11 |
+
#ifdef USE_CLEARCOAT
|
12 |
+
|
13 |
+
geometryClearcoatNormal = clearcoatNormal;
|
14 |
+
|
15 |
+
#endif
|
16 |
+
|
17 |
+
#ifdef USE_IRIDESCENCE
|
18 |
+
float dotNVi = saturate( dot( normal, geometryViewDir ) );
|
19 |
+
if ( material.iridescenceThickness == 0.0 ) {
|
20 |
+
material.iridescence = 0.0;
|
21 |
+
} else {
|
22 |
+
material.iridescence = saturate( material.iridescence );
|
23 |
+
}
|
24 |
+
if ( material.iridescence > 0.0 ) {
|
25 |
+
material.iridescenceFresnel = evalIridescence( 1.0, material.iridescenceIOR, dotNVi, material.iridescenceThickness, material.specularColor );
|
26 |
+
// Iridescence F0 approximation
|
27 |
+
material.iridescenceF0 = Schlick_to_F0( material.iridescenceFresnel, 1.0, dotNVi );
|
28 |
+
}
|
29 |
+
#endif
|
30 |
+
|
31 |
+
IncidentLight directLight;
|
32 |
+
|
33 |
+
#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )
|
34 |
+
|
35 |
+
PointLight pointLight;
|
36 |
+
#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0
|
37 |
+
PointLightShadow pointLightShadow;
|
38 |
+
#endif
|
39 |
+
|
40 |
+
#pragma unroll_loop_start
|
41 |
+
for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
|
42 |
+
|
43 |
+
pointLight = pointLights[ i ];
|
44 |
+
|
45 |
+
getPointLightInfo( pointLight, geometryPosition, directLight );
|
46 |
+
|
47 |
+
#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )
|
48 |
+
pointLightShadow = pointLightShadows[ i ];
|
49 |
+
directLight.color *= ( directLight.visible && receiveShadow ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;
|
50 |
+
#endif
|
51 |
+
|
52 |
+
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
53 |
+
|
54 |
+
}
|
55 |
+
#pragma unroll_loop_end
|
56 |
+
|
57 |
+
#endif
|
58 |
+
|
59 |
+
#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
|
60 |
+
|
61 |
+
SpotLight spotLight;
|
62 |
+
vec4 spotColor;
|
63 |
+
vec3 spotLightCoord;
|
64 |
+
bool inSpotLightMap;
|
65 |
+
|
66 |
+
#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0
|
67 |
+
SpotLightShadow spotLightShadow;
|
68 |
+
#endif
|
69 |
+
|
70 |
+
#pragma unroll_loop_start
|
71 |
+
for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {
|
72 |
+
|
73 |
+
spotLight = spotLights[ i ];
|
74 |
+
|
75 |
+
getSpotLightInfo( spotLight, geometryPosition, directLight );
|
76 |
+
|
77 |
+
// spot lights are ordered [shadows with maps, shadows without maps, maps without shadows, none]
|
78 |
+
#if ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )
|
79 |
+
#define SPOT_LIGHT_MAP_INDEX UNROLLED_LOOP_INDEX
|
80 |
+
#elif ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
|
81 |
+
#define SPOT_LIGHT_MAP_INDEX NUM_SPOT_LIGHT_MAPS
|
82 |
+
#else
|
83 |
+
#define SPOT_LIGHT_MAP_INDEX ( UNROLLED_LOOP_INDEX - NUM_SPOT_LIGHT_SHADOWS + NUM_SPOT_LIGHT_SHADOWS_WITH_MAPS )
|
84 |
+
#endif
|
85 |
+
#if ( SPOT_LIGHT_MAP_INDEX < NUM_SPOT_LIGHT_MAPS )
|
86 |
+
spotLightCoord = vSpotLightCoord[ i ].xyz / vSpotLightCoord[ i ].w;
|
87 |
+
inSpotLightMap = all( lessThan( abs( spotLightCoord * 2. - 1. ), vec3( 1.0 ) ) );
|
88 |
+
spotColor = texture2D( spotLightMap[ SPOT_LIGHT_MAP_INDEX ], spotLightCoord.xy );
|
89 |
+
directLight.color = inSpotLightMap ? directLight.color * spotColor.rgb : directLight.color;
|
90 |
+
#endif
|
91 |
+
#undef SPOT_LIGHT_MAP_INDEX
|
92 |
+
|
93 |
+
#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
|
94 |
+
spotLightShadow = spotLightShadows[ i ];
|
95 |
+
directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowIntensity, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotLightCoord[ i ] ) : 1.0;
|
96 |
+
|
97 |
+
#endif
|
98 |
+
|
99 |
+
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
100 |
+
|
101 |
+
}
|
102 |
+
#pragma unroll_loop_end
|
103 |
+
|
104 |
+
#endif
|
105 |
+
|
106 |
+
#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES )
|
107 |
+
|
108 |
+
DirectionalLight directionalLight;
|
109 |
+
float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear);
|
110 |
+
#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
|
111 |
+
DirectionalLightShadow directionalLightShadow;
|
112 |
+
#endif
|
113 |
+
|
114 |
+
#if defined( USE_SHADOWMAP ) && defined( CSM_FADE )
|
115 |
+
vec2 cascade;
|
116 |
+
float cascadeCenter;
|
117 |
+
float closestEdge;
|
118 |
+
float margin;
|
119 |
+
float csmx;
|
120 |
+
float csmy;
|
121 |
+
|
122 |
+
#pragma unroll_loop_start
|
123 |
+
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
|
124 |
+
|
125 |
+
directionalLight = directionalLights[ i ];
|
126 |
+
getDirectionalLightInfo( directionalLight, directLight );
|
127 |
+
|
128 |
+
#if ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
|
129 |
+
// NOTE: Depth gets larger away from the camera.
|
130 |
+
// cascade.x is closer, cascade.y is further
|
131 |
+
cascade = CSM_cascades[ i ];
|
132 |
+
cascadeCenter = ( cascade.x + cascade.y ) / 2.0;
|
133 |
+
closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y;
|
134 |
+
margin = 0.25 * pow( closestEdge, 2.0 );
|
135 |
+
csmx = cascade.x - margin / 2.0;
|
136 |
+
csmy = cascade.y + margin / 2.0;
|
137 |
+
if( linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) {
|
138 |
+
|
139 |
+
float dist = min( linearDepth - csmx, csmy - linearDepth );
|
140 |
+
float ratio = clamp( dist / margin, 0.0, 1.0 );
|
141 |
+
|
142 |
+
vec3 prevColor = directLight.color;
|
143 |
+
directionalLightShadow = directionalLightShadows[ i ];
|
144 |
+
directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
|
145 |
+
|
146 |
+
bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter;
|
147 |
+
directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 );
|
148 |
+
|
149 |
+
ReflectedLight prevLight = reflectedLight;
|
150 |
+
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
151 |
+
|
152 |
+
bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter;
|
153 |
+
float blendRatio = shouldBlend ? ratio : 1.0;
|
154 |
+
|
155 |
+
reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio );
|
156 |
+
reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio );
|
157 |
+
reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio );
|
158 |
+
reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio );
|
159 |
+
|
160 |
+
}
|
161 |
+
#endif
|
162 |
+
|
163 |
+
}
|
164 |
+
#pragma unroll_loop_end
|
165 |
+
#elif defined (USE_SHADOWMAP)
|
166 |
+
|
167 |
+
#pragma unroll_loop_start
|
168 |
+
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
|
169 |
+
|
170 |
+
directionalLight = directionalLights[ i ];
|
171 |
+
getDirectionalLightInfo( directionalLight, directLight );
|
172 |
+
|
173 |
+
#if ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
|
174 |
+
|
175 |
+
directionalLightShadow = directionalLightShadows[ i ];
|
176 |
+
if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
|
177 |
+
|
178 |
+
if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
179 |
+
|
180 |
+
#endif
|
181 |
+
|
182 |
+
}
|
183 |
+
#pragma unroll_loop_end
|
184 |
+
|
185 |
+
#elif ( NUM_DIR_LIGHT_SHADOWS > 0 )
|
186 |
+
// note: no loop here - all CSM lights are in fact one light only
|
187 |
+
getDirectionalLightInfo( directionalLights[0], directLight );
|
188 |
+
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
189 |
+
|
190 |
+
#endif
|
191 |
+
|
192 |
+
#if ( NUM_DIR_LIGHTS > NUM_DIR_LIGHT_SHADOWS)
|
193 |
+
// compute the lights not casting shadows (if any)
|
194 |
+
|
195 |
+
#pragma unroll_loop_start
|
196 |
+
for ( int i = NUM_DIR_LIGHT_SHADOWS; i < NUM_DIR_LIGHTS; i ++ ) {
|
197 |
+
|
198 |
+
directionalLight = directionalLights[ i ];
|
199 |
+
|
200 |
+
getDirectionalLightInfo( directionalLight, directLight );
|
201 |
+
|
202 |
+
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
203 |
+
|
204 |
+
}
|
205 |
+
#pragma unroll_loop_end
|
206 |
+
|
207 |
+
#endif
|
208 |
+
|
209 |
+
#endif
|
210 |
+
|
211 |
+
|
212 |
+
#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES )
|
213 |
+
|
214 |
+
DirectionalLight directionalLight;
|
215 |
+
#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
|
216 |
+
DirectionalLightShadow directionalLightShadow;
|
217 |
+
#endif
|
218 |
+
|
219 |
+
#pragma unroll_loop_start
|
220 |
+
for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
|
221 |
+
|
222 |
+
directionalLight = directionalLights[ i ];
|
223 |
+
|
224 |
+
getDirectionalLightInfo( directionalLight, directLight );
|
225 |
+
|
226 |
+
#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
|
227 |
+
directionalLightShadow = directionalLightShadows[ i ];
|
228 |
+
directLight.color *= ( directLight.visible && receiveShadow ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowIntensity, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
|
229 |
+
#endif
|
230 |
+
|
231 |
+
RE_Direct( directLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
232 |
+
|
233 |
+
}
|
234 |
+
#pragma unroll_loop_end
|
235 |
+
|
236 |
+
#endif
|
237 |
+
|
238 |
+
#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )
|
239 |
+
|
240 |
+
RectAreaLight rectAreaLight;
|
241 |
+
|
242 |
+
#pragma unroll_loop_start
|
243 |
+
for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
|
244 |
+
|
245 |
+
rectAreaLight = rectAreaLights[ i ];
|
246 |
+
RE_Direct_RectArea( rectAreaLight, geometryPosition, geometryNormal, geometryViewDir, geometryClearcoatNormal, material, reflectedLight );
|
247 |
+
|
248 |
+
}
|
249 |
+
#pragma unroll_loop_end
|
250 |
+
|
251 |
+
#endif
|
252 |
+
|
253 |
+
#if defined( RE_IndirectDiffuse )
|
254 |
+
|
255 |
+
vec3 iblIrradiance = vec3( 0.0 );
|
256 |
+
|
257 |
+
vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
|
258 |
+
|
259 |
+
#if defined( USE_LIGHT_PROBES )
|
260 |
+
|
261 |
+
irradiance += getLightProbeIrradiance( lightProbe, geometryNormal );
|
262 |
+
|
263 |
+
#endif
|
264 |
+
|
265 |
+
#if ( NUM_HEMI_LIGHTS > 0 )
|
266 |
+
|
267 |
+
#pragma unroll_loop_start
|
268 |
+
for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
|
269 |
+
|
270 |
+
irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometryNormal );
|
271 |
+
|
272 |
+
}
|
273 |
+
#pragma unroll_loop_end
|
274 |
+
|
275 |
+
#endif
|
276 |
+
|
277 |
+
#endif
|
278 |
+
|
279 |
+
#if defined( RE_IndirectSpecular )
|
280 |
+
|
281 |
+
vec3 radiance = vec3( 0.0 );
|
282 |
+
vec3 clearcoatRadiance = vec3( 0.0 );
|
283 |
+
|
284 |
+
#endif
|
285 |
+
`,
|
286 |
+
lights_pars_begin: /* glsl */`
|
287 |
+
#if defined( USE_CSM ) && defined( CSM_CASCADES )
|
288 |
+
uniform vec2 CSM_cascades[CSM_CASCADES];
|
289 |
+
uniform float cameraNear;
|
290 |
+
uniform float shadowFar;
|
291 |
+
#endif
|
292 |
+
` + ShaderChunk.lights_pars_begin
|
293 |
+
};
|
294 |
+
|
295 |
+
export { CSMShader };
|
libs/three.js/0.172.0/jsm/csm/CSMShadowNode.js
ADDED
@@ -0,0 +1,442 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Vector2,
|
3 |
+
Vector3,
|
4 |
+
MathUtils,
|
5 |
+
Matrix4,
|
6 |
+
Box3,
|
7 |
+
Object3D,
|
8 |
+
WebGLCoordinateSystem,
|
9 |
+
ShadowBaseNode
|
10 |
+
} from 'three/webgpu';
|
11 |
+
|
12 |
+
import { CSMFrustum } from './CSMFrustum.js';
|
13 |
+
import { viewZToOrthographicDepth, reference, uniform, float, vec4, vec2, If, Fn, min, renderGroup, positionView, shadow } from 'three/tsl';
|
14 |
+
|
15 |
+
const _cameraToLightMatrix = new Matrix4();
|
16 |
+
const _lightSpaceFrustum = new CSMFrustum();
|
17 |
+
const _center = new Vector3();
|
18 |
+
const _bbox = new Box3();
|
19 |
+
const _uniformArray = [];
|
20 |
+
const _logArray = [];
|
21 |
+
const _lightDirection = new Vector3();
|
22 |
+
const _lightOrientationMatrix = new Matrix4();
|
23 |
+
const _lightOrientationMatrixInverse = new Matrix4();
|
24 |
+
const _up = new Vector3( 0, 1, 0 );
|
25 |
+
|
26 |
+
class LwLight extends Object3D {
|
27 |
+
|
28 |
+
constructor() {
|
29 |
+
|
30 |
+
super();
|
31 |
+
|
32 |
+
this.target = new Object3D();
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
}
|
37 |
+
|
38 |
+
class CSMShadowNode extends ShadowBaseNode {
|
39 |
+
|
40 |
+
constructor( light, data = {} ) {
|
41 |
+
|
42 |
+
super( light );
|
43 |
+
|
44 |
+
this.camera = null;
|
45 |
+
this.cascades = data.cascades || 3;
|
46 |
+
this.maxFar = data.maxFar || 100000;
|
47 |
+
this.mode = data.mode || 'practical';
|
48 |
+
this.lightMargin = data.lightMargin || 200;
|
49 |
+
this.customSplitsCallback = data.customSplitsCallback;
|
50 |
+
|
51 |
+
this.fade = false;
|
52 |
+
|
53 |
+
this.breaks = [];
|
54 |
+
|
55 |
+
this._cascades = [];
|
56 |
+
this.mainFrustum = null;
|
57 |
+
this.frustums = [];
|
58 |
+
|
59 |
+
this.lights = [];
|
60 |
+
|
61 |
+
this._shadowNodes = [];
|
62 |
+
|
63 |
+
}
|
64 |
+
|
65 |
+
init( { camera, renderer } ) {
|
66 |
+
|
67 |
+
this.camera = camera;
|
68 |
+
|
69 |
+
const data = { webGL: renderer.coordinateSystem === WebGLCoordinateSystem };
|
70 |
+
this.mainFrustum = new CSMFrustum( data );
|
71 |
+
|
72 |
+
const light = this.light;
|
73 |
+
const parent = light.parent;
|
74 |
+
|
75 |
+
for ( let i = 0; i < this.cascades; i ++ ) {
|
76 |
+
|
77 |
+
const lwLight = new LwLight();
|
78 |
+
lwLight.castShadow = true;
|
79 |
+
|
80 |
+
const lShadow = light.shadow.clone();
|
81 |
+
lShadow.bias = lShadow.bias * ( i + 1 );
|
82 |
+
|
83 |
+
this.lights.push( lwLight );
|
84 |
+
|
85 |
+
parent.add( lwLight );
|
86 |
+
parent.add( lwLight.target );
|
87 |
+
|
88 |
+
lwLight.shadow = lShadow;
|
89 |
+
|
90 |
+
this._shadowNodes.push( shadow( lwLight, lShadow ) );
|
91 |
+
|
92 |
+
this._cascades.push( new Vector2() );
|
93 |
+
|
94 |
+
}
|
95 |
+
|
96 |
+
this.updateFrustums();
|
97 |
+
|
98 |
+
}
|
99 |
+
|
100 |
+
initCascades() {
|
101 |
+
|
102 |
+
const camera = this.camera;
|
103 |
+
camera.updateProjectionMatrix();
|
104 |
+
|
105 |
+
this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar );
|
106 |
+
this.mainFrustum.split( this.breaks, this.frustums );
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
getBreaks() {
|
111 |
+
|
112 |
+
const camera = this.camera;
|
113 |
+
const far = Math.min( camera.far, this.maxFar );
|
114 |
+
|
115 |
+
this.breaks.length = 0;
|
116 |
+
|
117 |
+
switch ( this.mode ) {
|
118 |
+
|
119 |
+
case 'uniform':
|
120 |
+
uniformSplit( this.cascades, camera.near, far, this.breaks );
|
121 |
+
break;
|
122 |
+
|
123 |
+
case 'logarithmic':
|
124 |
+
logarithmicSplit( this.cascades, camera.near, far, this.breaks );
|
125 |
+
break;
|
126 |
+
|
127 |
+
case 'practical':
|
128 |
+
practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks );
|
129 |
+
break;
|
130 |
+
|
131 |
+
case 'custom':
|
132 |
+
if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
|
133 |
+
this.customSplitsCallback( this.cascades, camera.near, far, this.breaks );
|
134 |
+
break;
|
135 |
+
|
136 |
+
}
|
137 |
+
|
138 |
+
function uniformSplit( amount, near, far, target ) {
|
139 |
+
|
140 |
+
for ( let i = 1; i < amount; i ++ ) {
|
141 |
+
|
142 |
+
target.push( ( near + ( far - near ) * i / amount ) / far );
|
143 |
+
|
144 |
+
}
|
145 |
+
|
146 |
+
target.push( 1 );
|
147 |
+
|
148 |
+
}
|
149 |
+
|
150 |
+
function logarithmicSplit( amount, near, far, target ) {
|
151 |
+
|
152 |
+
for ( let i = 1; i < amount; i ++ ) {
|
153 |
+
|
154 |
+
target.push( ( near * ( far / near ) ** ( i / amount ) ) / far );
|
155 |
+
|
156 |
+
}
|
157 |
+
|
158 |
+
target.push( 1 );
|
159 |
+
|
160 |
+
}
|
161 |
+
|
162 |
+
function practicalSplit( amount, near, far, lambda, target ) {
|
163 |
+
|
164 |
+
_uniformArray.length = 0;
|
165 |
+
_logArray.length = 0;
|
166 |
+
logarithmicSplit( amount, near, far, _logArray );
|
167 |
+
uniformSplit( amount, near, far, _uniformArray );
|
168 |
+
|
169 |
+
for ( let i = 1; i < amount; i ++ ) {
|
170 |
+
|
171 |
+
target.push( MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) );
|
172 |
+
|
173 |
+
}
|
174 |
+
|
175 |
+
target.push( 1 );
|
176 |
+
|
177 |
+
}
|
178 |
+
|
179 |
+
}
|
180 |
+
|
181 |
+
setLightBreaks() {
|
182 |
+
|
183 |
+
for ( let i = 0, l = this.cascades; i < l; i ++ ) {
|
184 |
+
|
185 |
+
const amount = this.breaks[ i ];
|
186 |
+
const prev = this.breaks[ i - 1 ] || 0;
|
187 |
+
|
188 |
+
this._cascades[ i ].set( prev, amount );
|
189 |
+
|
190 |
+
}
|
191 |
+
|
192 |
+
}
|
193 |
+
|
194 |
+
updateShadowBounds() {
|
195 |
+
|
196 |
+
const frustums = this.frustums;
|
197 |
+
|
198 |
+
for ( let i = 0; i < frustums.length; i ++ ) {
|
199 |
+
|
200 |
+
const shadowCam = this.lights[ i ].shadow.camera;
|
201 |
+
const frustum = this.frustums[ i ];
|
202 |
+
|
203 |
+
// Get the two points that represent that furthest points on the frustum assuming
|
204 |
+
// that's either the diagonal across the far plane or the diagonal across the whole
|
205 |
+
// frustum itself.
|
206 |
+
const nearVerts = frustum.vertices.near;
|
207 |
+
const farVerts = frustum.vertices.far;
|
208 |
+
const point1 = farVerts[ 0 ];
|
209 |
+
|
210 |
+
let point2;
|
211 |
+
|
212 |
+
if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) {
|
213 |
+
|
214 |
+
point2 = farVerts[ 2 ];
|
215 |
+
|
216 |
+
} else {
|
217 |
+
|
218 |
+
point2 = nearVerts[ 2 ];
|
219 |
+
|
220 |
+
}
|
221 |
+
|
222 |
+
let squaredBBWidth = point1.distanceTo( point2 );
|
223 |
+
|
224 |
+
if ( this.fade ) {
|
225 |
+
|
226 |
+
// expand the shadow extents by the fade margin if fade is enabled.
|
227 |
+
const camera = this.camera;
|
228 |
+
const far = Math.max( camera.far, this.maxFar );
|
229 |
+
const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near );
|
230 |
+
const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near );
|
231 |
+
|
232 |
+
squaredBBWidth += margin;
|
233 |
+
|
234 |
+
}
|
235 |
+
|
236 |
+
shadowCam.left = - squaredBBWidth / 2;
|
237 |
+
shadowCam.right = squaredBBWidth / 2;
|
238 |
+
shadowCam.top = squaredBBWidth / 2;
|
239 |
+
shadowCam.bottom = - squaredBBWidth / 2;
|
240 |
+
shadowCam.updateProjectionMatrix();
|
241 |
+
|
242 |
+
}
|
243 |
+
|
244 |
+
}
|
245 |
+
|
246 |
+
updateFrustums() {
|
247 |
+
|
248 |
+
this.getBreaks();
|
249 |
+
this.initCascades();
|
250 |
+
this.updateShadowBounds();
|
251 |
+
this.setLightBreaks();
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
setupFade() {
|
256 |
+
|
257 |
+
const cameraNear = reference( 'camera.near', 'float', this ).setGroup( renderGroup );
|
258 |
+
const cascades = reference( '_cascades', 'vec2', this ).setGroup( renderGroup ).label( 'cascades' );
|
259 |
+
|
260 |
+
const shadowFar = uniform( 'float' ).setGroup( renderGroup ).label( 'shadowFar' )
|
261 |
+
.onRenderUpdate( () => Math.min( this.maxFar, this.camera.far ) );
|
262 |
+
|
263 |
+
const linearDepth = viewZToOrthographicDepth( positionView.z, cameraNear, shadowFar ).toVar( 'linearDepth' );
|
264 |
+
const lastCascade = this.cascades - 1;
|
265 |
+
|
266 |
+
return Fn( ( builder ) => {
|
267 |
+
|
268 |
+
this.setupShadowPosition( builder );
|
269 |
+
|
270 |
+
const ret = vec4( 1, 1, 1, 1 ).toVar( 'shadowValue' );
|
271 |
+
|
272 |
+
const cascade = vec2().toVar( 'cascade' );
|
273 |
+
const cascadeCenter = float().toVar( 'cascadeCenter' );
|
274 |
+
|
275 |
+
const margin = float().toVar( 'margin' );
|
276 |
+
|
277 |
+
const csmX = float().toVar( 'csmX' );
|
278 |
+
const csmY = float().toVar( 'csmY' );
|
279 |
+
|
280 |
+
for ( let i = 0; i < this.cascades; i ++ ) {
|
281 |
+
|
282 |
+
const isLastCascade = i === lastCascade;
|
283 |
+
|
284 |
+
cascade.assign( cascades.element( i ) );
|
285 |
+
|
286 |
+
cascadeCenter.assign( cascade.x.add( cascade.y ).div( 2.0 ) );
|
287 |
+
|
288 |
+
const closestEdge = linearDepth.lessThan( cascadeCenter ).select( cascade.x, cascade.y );
|
289 |
+
|
290 |
+
margin.assign( float( 0.25 ).mul( closestEdge.pow( 2.0 ) ) );
|
291 |
+
|
292 |
+
csmX.assign( cascade.x.sub( margin.div( 2.0 ) ) );
|
293 |
+
|
294 |
+
if ( isLastCascade ) {
|
295 |
+
|
296 |
+
csmY.assign( cascade.y );
|
297 |
+
|
298 |
+
} else {
|
299 |
+
|
300 |
+
csmY.assign( cascade.y.add( margin.div( 2.0 ) ) );
|
301 |
+
|
302 |
+
}
|
303 |
+
|
304 |
+
const inRange = linearDepth.greaterThanEqual( csmX ).and( linearDepth.lessThanEqual( csmY ) );
|
305 |
+
|
306 |
+
If( inRange, () => {
|
307 |
+
|
308 |
+
const dist = min( linearDepth.sub( csmX ), csmY.sub( linearDepth ) ).toVar();
|
309 |
+
|
310 |
+
let ratio = dist.div( margin ).clamp( 0.0, 1.0 );
|
311 |
+
|
312 |
+
if ( i === 0 ) {
|
313 |
+
|
314 |
+
// don't fade at nearest edge
|
315 |
+
ratio = linearDepth.greaterThan( cascadeCenter ).select( ratio, 1 );
|
316 |
+
|
317 |
+
}
|
318 |
+
|
319 |
+
ret.subAssign( this._shadowNodes[ i ].oneMinus().mul( ratio ) );
|
320 |
+
|
321 |
+
} );
|
322 |
+
|
323 |
+
}
|
324 |
+
|
325 |
+
return ret;
|
326 |
+
|
327 |
+
} )();
|
328 |
+
|
329 |
+
}
|
330 |
+
|
331 |
+
setupStandard() {
|
332 |
+
|
333 |
+
const cameraNear = reference( 'camera.near', 'float', this ).setGroup( renderGroup );
|
334 |
+
const cascades = reference( '_cascades', 'vec2', this ).setGroup( renderGroup ).label( 'cascades' );
|
335 |
+
|
336 |
+
const shadowFar = uniform( 'float' ).setGroup( renderGroup ).label( 'shadowFar' )
|
337 |
+
.onRenderUpdate( () => Math.min( this.maxFar, this.camera.far ) );
|
338 |
+
|
339 |
+
const linearDepth = viewZToOrthographicDepth( positionView.z, cameraNear, shadowFar ).toVar( 'linearDepth' );
|
340 |
+
|
341 |
+
return Fn( ( builder ) => {
|
342 |
+
|
343 |
+
this.setupShadowPosition( builder );
|
344 |
+
|
345 |
+
const ret = vec4( 1, 1, 1, 1 ).toVar( 'shadowValue' );
|
346 |
+
const cascade = vec2().toVar( 'cascade' );
|
347 |
+
|
348 |
+
for ( let i = 0; i < this.cascades; i ++ ) {
|
349 |
+
|
350 |
+
cascade.assign( cascades.element( i ) );
|
351 |
+
|
352 |
+
If( linearDepth.greaterThanEqual( cascade.x ).and( linearDepth.lessThanEqual( cascade.y ) ), () => {
|
353 |
+
|
354 |
+
ret.assign( this._shadowNodes[ i ] );
|
355 |
+
|
356 |
+
} );
|
357 |
+
|
358 |
+
}
|
359 |
+
|
360 |
+
return ret;
|
361 |
+
|
362 |
+
} )();
|
363 |
+
|
364 |
+
}
|
365 |
+
|
366 |
+
setup( builder ) {
|
367 |
+
|
368 |
+
if ( this.camera === null ) this.init( builder );
|
369 |
+
|
370 |
+
return this.fade === true ? this.setupFade() : this.setupStandard();
|
371 |
+
|
372 |
+
}
|
373 |
+
|
374 |
+
updateBefore( /*builder*/ ) {
|
375 |
+
|
376 |
+
const light = this.light;
|
377 |
+
const camera = this.camera;
|
378 |
+
const frustums = this.frustums;
|
379 |
+
|
380 |
+
_lightDirection.subVectors( light.target.position, light.position ).normalize();
|
381 |
+
|
382 |
+
// for each frustum we need to find its min-max box aligned with the light orientation
|
383 |
+
// the position in _lightOrientationMatrix does not matter, as we transform there and back
|
384 |
+
_lightOrientationMatrix.lookAt( light.position, light.target.position, _up );
|
385 |
+
_lightOrientationMatrixInverse.copy( _lightOrientationMatrix ).invert();
|
386 |
+
|
387 |
+
for ( let i = 0; i < frustums.length; i ++ ) {
|
388 |
+
|
389 |
+
const lwLight = this.lights[ i ];
|
390 |
+
const shadow = lwLight.shadow;
|
391 |
+
const shadowCam = shadow.camera;
|
392 |
+
const texelWidth = ( shadowCam.right - shadowCam.left ) / shadow.mapSize.width;
|
393 |
+
const texelHeight = ( shadowCam.top - shadowCam.bottom ) / shadow.mapSize.height;
|
394 |
+
|
395 |
+
_cameraToLightMatrix.multiplyMatrices( _lightOrientationMatrixInverse, camera.matrixWorld );
|
396 |
+
frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum );
|
397 |
+
|
398 |
+
const nearVerts = _lightSpaceFrustum.vertices.near;
|
399 |
+
const farVerts = _lightSpaceFrustum.vertices.far;
|
400 |
+
|
401 |
+
_bbox.makeEmpty();
|
402 |
+
|
403 |
+
for ( let j = 0; j < 4; j ++ ) {
|
404 |
+
|
405 |
+
_bbox.expandByPoint( nearVerts[ j ] );
|
406 |
+
_bbox.expandByPoint( farVerts[ j ] );
|
407 |
+
|
408 |
+
}
|
409 |
+
|
410 |
+
_bbox.getCenter( _center );
|
411 |
+
_center.z = _bbox.max.z + this.lightMargin;
|
412 |
+
_center.x = Math.floor( _center.x / texelWidth ) * texelWidth;
|
413 |
+
_center.y = Math.floor( _center.y / texelHeight ) * texelHeight;
|
414 |
+
_center.applyMatrix4( _lightOrientationMatrix );
|
415 |
+
|
416 |
+
lwLight.position.copy( _center );
|
417 |
+
lwLight.target.position.copy( _center );
|
418 |
+
lwLight.target.position.add( _lightDirection );
|
419 |
+
|
420 |
+
}
|
421 |
+
|
422 |
+
}
|
423 |
+
|
424 |
+
dispose() {
|
425 |
+
|
426 |
+
for ( let i = 0; i < this.lights.length; i ++ ) {
|
427 |
+
|
428 |
+
const light = this.lights[ i ];
|
429 |
+
const parent = light.parent;
|
430 |
+
|
431 |
+
parent.remove( light.target );
|
432 |
+
parent.remove( light );
|
433 |
+
|
434 |
+
}
|
435 |
+
|
436 |
+
super.dispose();
|
437 |
+
|
438 |
+
}
|
439 |
+
|
440 |
+
}
|
441 |
+
|
442 |
+
export { CSMShadowNode };
|
libs/three.js/0.172.0/jsm/curves/CurveExtras.js
ADDED
@@ -0,0 +1,422 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Curve,
|
3 |
+
Vector3
|
4 |
+
} from 'three';
|
5 |
+
|
6 |
+
/**
|
7 |
+
* A bunch of parametric curves
|
8 |
+
*
|
9 |
+
* Formulas collected from various sources
|
10 |
+
* http://mathworld.wolfram.com/HeartCurve.html
|
11 |
+
* http://en.wikipedia.org/wiki/Viviani%27s_curve
|
12 |
+
* http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf
|
13 |
+
* https://prideout.net/blog/old/blog/index.html@p=44.html
|
14 |
+
*/
|
15 |
+
|
16 |
+
// GrannyKnot
|
17 |
+
|
18 |
+
class GrannyKnot extends Curve {
|
19 |
+
|
20 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
21 |
+
|
22 |
+
const point = optionalTarget;
|
23 |
+
|
24 |
+
t = 2 * Math.PI * t;
|
25 |
+
|
26 |
+
const x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t );
|
27 |
+
const y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t );
|
28 |
+
const z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t );
|
29 |
+
|
30 |
+
return point.set( x, y, z ).multiplyScalar( 20 );
|
31 |
+
|
32 |
+
}
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
// HeartCurve
|
37 |
+
|
38 |
+
class HeartCurve extends Curve {
|
39 |
+
|
40 |
+
constructor( scale = 5 ) {
|
41 |
+
|
42 |
+
super();
|
43 |
+
|
44 |
+
this.scale = scale;
|
45 |
+
|
46 |
+
}
|
47 |
+
|
48 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
49 |
+
|
50 |
+
const point = optionalTarget;
|
51 |
+
|
52 |
+
t *= 2 * Math.PI;
|
53 |
+
|
54 |
+
const x = 16 * Math.pow( Math.sin( t ), 3 );
|
55 |
+
const y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t );
|
56 |
+
const z = 0;
|
57 |
+
|
58 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
}
|
63 |
+
|
64 |
+
// Viviani's Curve
|
65 |
+
|
66 |
+
class VivianiCurve extends Curve {
|
67 |
+
|
68 |
+
constructor( scale = 70 ) {
|
69 |
+
|
70 |
+
super();
|
71 |
+
|
72 |
+
this.scale = scale;
|
73 |
+
|
74 |
+
}
|
75 |
+
|
76 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
77 |
+
|
78 |
+
const point = optionalTarget;
|
79 |
+
|
80 |
+
t = t * 4 * Math.PI; // normalized to 0..1
|
81 |
+
const a = this.scale / 2;
|
82 |
+
|
83 |
+
const x = a * ( 1 + Math.cos( t ) );
|
84 |
+
const y = a * Math.sin( t );
|
85 |
+
const z = 2 * a * Math.sin( t / 2 );
|
86 |
+
|
87 |
+
return point.set( x, y, z );
|
88 |
+
|
89 |
+
}
|
90 |
+
|
91 |
+
}
|
92 |
+
|
93 |
+
// KnotCurve
|
94 |
+
|
95 |
+
class KnotCurve extends Curve {
|
96 |
+
|
97 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
98 |
+
|
99 |
+
const point = optionalTarget;
|
100 |
+
|
101 |
+
t *= 2 * Math.PI;
|
102 |
+
|
103 |
+
const R = 10;
|
104 |
+
const s = 50;
|
105 |
+
|
106 |
+
const x = s * Math.sin( t );
|
107 |
+
const y = Math.cos( t ) * ( R + s * Math.cos( t ) );
|
108 |
+
const z = Math.sin( t ) * ( R + s * Math.cos( t ) );
|
109 |
+
|
110 |
+
return point.set( x, y, z );
|
111 |
+
|
112 |
+
}
|
113 |
+
|
114 |
+
}
|
115 |
+
|
116 |
+
|
117 |
+
// HelixCurve
|
118 |
+
|
119 |
+
class HelixCurve extends Curve {
|
120 |
+
|
121 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
122 |
+
|
123 |
+
const point = optionalTarget;
|
124 |
+
|
125 |
+
const a = 30; // radius
|
126 |
+
const b = 150; // height
|
127 |
+
|
128 |
+
const t2 = 2 * Math.PI * t * b / 30;
|
129 |
+
|
130 |
+
const x = Math.cos( t2 ) * a;
|
131 |
+
const y = Math.sin( t2 ) * a;
|
132 |
+
const z = b * t;
|
133 |
+
|
134 |
+
return point.set( x, y, z );
|
135 |
+
|
136 |
+
}
|
137 |
+
|
138 |
+
}
|
139 |
+
|
140 |
+
// TrefoilKnot
|
141 |
+
|
142 |
+
class TrefoilKnot extends Curve {
|
143 |
+
|
144 |
+
constructor( scale = 10 ) {
|
145 |
+
|
146 |
+
super();
|
147 |
+
|
148 |
+
this.scale = scale;
|
149 |
+
|
150 |
+
}
|
151 |
+
|
152 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
153 |
+
|
154 |
+
const point = optionalTarget;
|
155 |
+
|
156 |
+
t *= Math.PI * 2;
|
157 |
+
|
158 |
+
const x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t );
|
159 |
+
const y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t );
|
160 |
+
const z = Math.sin( 3 * t );
|
161 |
+
|
162 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
163 |
+
|
164 |
+
}
|
165 |
+
|
166 |
+
}
|
167 |
+
|
168 |
+
// TorusKnot
|
169 |
+
|
170 |
+
class TorusKnot extends Curve {
|
171 |
+
|
172 |
+
constructor( scale = 10 ) {
|
173 |
+
|
174 |
+
super();
|
175 |
+
|
176 |
+
this.scale = scale;
|
177 |
+
|
178 |
+
}
|
179 |
+
|
180 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
181 |
+
|
182 |
+
const point = optionalTarget;
|
183 |
+
|
184 |
+
const p = 3;
|
185 |
+
const q = 4;
|
186 |
+
|
187 |
+
t *= Math.PI * 2;
|
188 |
+
|
189 |
+
const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
|
190 |
+
const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
|
191 |
+
const z = Math.sin( q * t );
|
192 |
+
|
193 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
// CinquefoilKnot
|
200 |
+
|
201 |
+
class CinquefoilKnot extends Curve {
|
202 |
+
|
203 |
+
constructor( scale = 10 ) {
|
204 |
+
|
205 |
+
super();
|
206 |
+
|
207 |
+
this.scale = scale;
|
208 |
+
|
209 |
+
}
|
210 |
+
|
211 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
212 |
+
|
213 |
+
const point = optionalTarget;
|
214 |
+
|
215 |
+
const p = 2;
|
216 |
+
const q = 5;
|
217 |
+
|
218 |
+
t *= Math.PI * 2;
|
219 |
+
|
220 |
+
const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
|
221 |
+
const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
|
222 |
+
const z = Math.sin( q * t );
|
223 |
+
|
224 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
}
|
229 |
+
|
230 |
+
|
231 |
+
// TrefoilPolynomialKnot
|
232 |
+
|
233 |
+
class TrefoilPolynomialKnot extends Curve {
|
234 |
+
|
235 |
+
constructor( scale = 10 ) {
|
236 |
+
|
237 |
+
super();
|
238 |
+
|
239 |
+
this.scale = scale;
|
240 |
+
|
241 |
+
}
|
242 |
+
|
243 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
244 |
+
|
245 |
+
const point = optionalTarget;
|
246 |
+
|
247 |
+
t = t * 4 - 2;
|
248 |
+
|
249 |
+
const x = Math.pow( t, 3 ) - 3 * t;
|
250 |
+
const y = Math.pow( t, 4 ) - 4 * t * t;
|
251 |
+
const z = 1 / 5 * Math.pow( t, 5 ) - 2 * t;
|
252 |
+
|
253 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
254 |
+
|
255 |
+
}
|
256 |
+
|
257 |
+
}
|
258 |
+
|
259 |
+
function scaleTo( x, y, t ) {
|
260 |
+
|
261 |
+
const r = y - x;
|
262 |
+
return t * r + x;
|
263 |
+
|
264 |
+
}
|
265 |
+
|
266 |
+
// FigureEightPolynomialKnot
|
267 |
+
|
268 |
+
class FigureEightPolynomialKnot extends Curve {
|
269 |
+
|
270 |
+
constructor( scale = 1 ) {
|
271 |
+
|
272 |
+
super();
|
273 |
+
|
274 |
+
this.scale = scale;
|
275 |
+
|
276 |
+
}
|
277 |
+
|
278 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
279 |
+
|
280 |
+
const point = optionalTarget;
|
281 |
+
|
282 |
+
t = scaleTo( - 4, 4, t );
|
283 |
+
|
284 |
+
const x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 );
|
285 |
+
const y = Math.pow( t, 4 ) - 13 * t * t;
|
286 |
+
const z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 );
|
287 |
+
|
288 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
289 |
+
|
290 |
+
}
|
291 |
+
|
292 |
+
}
|
293 |
+
|
294 |
+
// DecoratedTorusKnot4a
|
295 |
+
|
296 |
+
class DecoratedTorusKnot4a extends Curve {
|
297 |
+
|
298 |
+
constructor( scale = 40 ) {
|
299 |
+
|
300 |
+
super();
|
301 |
+
|
302 |
+
this.scale = scale;
|
303 |
+
|
304 |
+
}
|
305 |
+
|
306 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
307 |
+
|
308 |
+
const point = optionalTarget;
|
309 |
+
|
310 |
+
t *= Math.PI * 2;
|
311 |
+
|
312 |
+
const x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
|
313 |
+
const y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
|
314 |
+
const z = 0.35 * Math.sin( 5 * t );
|
315 |
+
|
316 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
317 |
+
|
318 |
+
}
|
319 |
+
|
320 |
+
}
|
321 |
+
|
322 |
+
// DecoratedTorusKnot4b
|
323 |
+
|
324 |
+
class DecoratedTorusKnot4b extends Curve {
|
325 |
+
|
326 |
+
constructor( scale = 40 ) {
|
327 |
+
|
328 |
+
super();
|
329 |
+
|
330 |
+
this.scale = scale;
|
331 |
+
|
332 |
+
}
|
333 |
+
|
334 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
335 |
+
|
336 |
+
const point = optionalTarget;
|
337 |
+
|
338 |
+
const fi = t * Math.PI * 2;
|
339 |
+
|
340 |
+
const x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
|
341 |
+
const y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
|
342 |
+
const z = 0.2 * Math.sin( 9 * fi );
|
343 |
+
|
344 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
}
|
349 |
+
|
350 |
+
|
351 |
+
// DecoratedTorusKnot5a
|
352 |
+
|
353 |
+
class DecoratedTorusKnot5a extends Curve {
|
354 |
+
|
355 |
+
constructor( scale = 40 ) {
|
356 |
+
|
357 |
+
super();
|
358 |
+
|
359 |
+
this.scale = scale;
|
360 |
+
|
361 |
+
}
|
362 |
+
|
363 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
364 |
+
|
365 |
+
const point = optionalTarget;
|
366 |
+
|
367 |
+
const fi = t * Math.PI * 2;
|
368 |
+
|
369 |
+
const x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
|
370 |
+
const y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
|
371 |
+
const z = 0.2 * Math.sin( 20 * fi );
|
372 |
+
|
373 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
374 |
+
|
375 |
+
}
|
376 |
+
|
377 |
+
}
|
378 |
+
|
379 |
+
// DecoratedTorusKnot5c
|
380 |
+
|
381 |
+
class DecoratedTorusKnot5c extends Curve {
|
382 |
+
|
383 |
+
constructor( scale = 40 ) {
|
384 |
+
|
385 |
+
super();
|
386 |
+
|
387 |
+
this.scale = scale;
|
388 |
+
|
389 |
+
}
|
390 |
+
|
391 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
392 |
+
|
393 |
+
const point = optionalTarget;
|
394 |
+
|
395 |
+
const fi = t * Math.PI * 2;
|
396 |
+
|
397 |
+
const x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
|
398 |
+
const y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
|
399 |
+
const z = 0.35 * Math.sin( 15 * fi );
|
400 |
+
|
401 |
+
return point.set( x, y, z ).multiplyScalar( this.scale );
|
402 |
+
|
403 |
+
}
|
404 |
+
|
405 |
+
}
|
406 |
+
|
407 |
+
export {
|
408 |
+
GrannyKnot,
|
409 |
+
HeartCurve,
|
410 |
+
VivianiCurve,
|
411 |
+
KnotCurve,
|
412 |
+
HelixCurve,
|
413 |
+
TrefoilKnot,
|
414 |
+
TorusKnot,
|
415 |
+
CinquefoilKnot,
|
416 |
+
TrefoilPolynomialKnot,
|
417 |
+
FigureEightPolynomialKnot,
|
418 |
+
DecoratedTorusKnot4a,
|
419 |
+
DecoratedTorusKnot4b,
|
420 |
+
DecoratedTorusKnot5a,
|
421 |
+
DecoratedTorusKnot5c
|
422 |
+
};
|
libs/three.js/0.172.0/jsm/curves/NURBSCurve.js
ADDED
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Curve,
|
3 |
+
Vector3,
|
4 |
+
Vector4
|
5 |
+
} from 'three';
|
6 |
+
import * as NURBSUtils from '../curves/NURBSUtils.js';
|
7 |
+
|
8 |
+
/**
|
9 |
+
* NURBS curve object
|
10 |
+
*
|
11 |
+
* Derives from Curve, overriding getPoint and getTangent.
|
12 |
+
*
|
13 |
+
* Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
|
14 |
+
*
|
15 |
+
**/
|
16 |
+
|
17 |
+
class NURBSCurve extends Curve {
|
18 |
+
|
19 |
+
constructor(
|
20 |
+
degree,
|
21 |
+
knots /* array of reals */,
|
22 |
+
controlPoints /* array of Vector(2|3|4) */,
|
23 |
+
startKnot /* index in knots */,
|
24 |
+
endKnot /* index in knots */
|
25 |
+
) {
|
26 |
+
|
27 |
+
super();
|
28 |
+
|
29 |
+
const knotsLength = knots ? knots.length - 1 : 0;
|
30 |
+
const pointsLength = controlPoints ? controlPoints.length : 0;
|
31 |
+
|
32 |
+
this.degree = degree;
|
33 |
+
this.knots = knots;
|
34 |
+
this.controlPoints = [];
|
35 |
+
// Used by periodic NURBS to remove hidden spans
|
36 |
+
this.startKnot = startKnot || 0;
|
37 |
+
this.endKnot = endKnot || knotsLength;
|
38 |
+
|
39 |
+
for ( let i = 0; i < pointsLength; ++ i ) {
|
40 |
+
|
41 |
+
// ensure Vector4 for control points
|
42 |
+
const point = controlPoints[ i ];
|
43 |
+
this.controlPoints[ i ] = new Vector4( point.x, point.y, point.z, point.w );
|
44 |
+
|
45 |
+
}
|
46 |
+
|
47 |
+
}
|
48 |
+
|
49 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
50 |
+
|
51 |
+
const point = optionalTarget;
|
52 |
+
|
53 |
+
const u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
|
54 |
+
|
55 |
+
// following results in (wx, wy, wz, w) homogeneous point
|
56 |
+
const hpoint = NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
|
57 |
+
|
58 |
+
if ( hpoint.w !== 1.0 ) {
|
59 |
+
|
60 |
+
// project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
|
61 |
+
hpoint.divideScalar( hpoint.w );
|
62 |
+
|
63 |
+
}
|
64 |
+
|
65 |
+
return point.set( hpoint.x, hpoint.y, hpoint.z );
|
66 |
+
|
67 |
+
}
|
68 |
+
|
69 |
+
getTangent( t, optionalTarget = new Vector3() ) {
|
70 |
+
|
71 |
+
const tangent = optionalTarget;
|
72 |
+
|
73 |
+
const u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
|
74 |
+
const ders = NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
|
75 |
+
tangent.copy( ders[ 1 ] ).normalize();
|
76 |
+
|
77 |
+
return tangent;
|
78 |
+
|
79 |
+
}
|
80 |
+
|
81 |
+
toJSON() {
|
82 |
+
|
83 |
+
const data = super.toJSON();
|
84 |
+
|
85 |
+
data.degree = this.degree;
|
86 |
+
data.knots = [ ...this.knots ];
|
87 |
+
data.controlPoints = this.controlPoints.map( p => p.toArray() );
|
88 |
+
data.startKnot = this.startKnot;
|
89 |
+
data.endKnot = this.endKnot;
|
90 |
+
|
91 |
+
return data;
|
92 |
+
|
93 |
+
}
|
94 |
+
|
95 |
+
fromJSON( json ) {
|
96 |
+
|
97 |
+
super.fromJSON( json );
|
98 |
+
|
99 |
+
this.degree = json.degree;
|
100 |
+
this.knots = [ ...json.knots ];
|
101 |
+
this.controlPoints = json.controlPoints.map( p => new Vector4( p[ 0 ], p[ 1 ], p[ 2 ], p[ 3 ] ) );
|
102 |
+
this.startKnot = json.startKnot;
|
103 |
+
this.endKnot = json.endKnot;
|
104 |
+
|
105 |
+
return this;
|
106 |
+
|
107 |
+
}
|
108 |
+
|
109 |
+
}
|
110 |
+
|
111 |
+
export { NURBSCurve };
|
libs/three.js/0.172.0/jsm/curves/NURBSSurface.js
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Vector4
|
3 |
+
} from 'three';
|
4 |
+
import * as NURBSUtils from '../curves/NURBSUtils.js';
|
5 |
+
|
6 |
+
/**
|
7 |
+
* NURBS surface object
|
8 |
+
*
|
9 |
+
* Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
|
10 |
+
**/
|
11 |
+
|
12 |
+
class NURBSSurface {
|
13 |
+
|
14 |
+
constructor( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) {
|
15 |
+
|
16 |
+
this.degree1 = degree1;
|
17 |
+
this.degree2 = degree2;
|
18 |
+
this.knots1 = knots1;
|
19 |
+
this.knots2 = knots2;
|
20 |
+
this.controlPoints = [];
|
21 |
+
|
22 |
+
const len1 = knots1.length - degree1 - 1;
|
23 |
+
const len2 = knots2.length - degree2 - 1;
|
24 |
+
|
25 |
+
// ensure Vector4 for control points
|
26 |
+
for ( let i = 0; i < len1; ++ i ) {
|
27 |
+
|
28 |
+
this.controlPoints[ i ] = [];
|
29 |
+
|
30 |
+
for ( let j = 0; j < len2; ++ j ) {
|
31 |
+
|
32 |
+
const point = controlPoints[ i ][ j ];
|
33 |
+
this.controlPoints[ i ][ j ] = new Vector4( point.x, point.y, point.z, point.w );
|
34 |
+
|
35 |
+
}
|
36 |
+
|
37 |
+
}
|
38 |
+
|
39 |
+
}
|
40 |
+
|
41 |
+
getPoint( t1, t2, target ) {
|
42 |
+
|
43 |
+
const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
|
44 |
+
const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u
|
45 |
+
|
46 |
+
NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target );
|
47 |
+
|
48 |
+
}
|
49 |
+
|
50 |
+
}
|
51 |
+
|
52 |
+
export { NURBSSurface };
|
libs/three.js/0.172.0/jsm/curves/NURBSUtils.js
ADDED
@@ -0,0 +1,545 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Vector3,
|
3 |
+
Vector4
|
4 |
+
} from 'three';
|
5 |
+
|
6 |
+
/**
|
7 |
+
* NURBS utils
|
8 |
+
*
|
9 |
+
* See NURBSCurve and NURBSSurface.
|
10 |
+
**/
|
11 |
+
|
12 |
+
|
13 |
+
/**************************************************************
|
14 |
+
* NURBS Utils
|
15 |
+
**************************************************************/
|
16 |
+
|
17 |
+
/*
|
18 |
+
Finds knot vector span.
|
19 |
+
|
20 |
+
p : degree
|
21 |
+
u : parametric value
|
22 |
+
U : knot vector
|
23 |
+
|
24 |
+
returns the span
|
25 |
+
*/
|
26 |
+
function findSpan( p, u, U ) {
|
27 |
+
|
28 |
+
const n = U.length - p - 1;
|
29 |
+
|
30 |
+
if ( u >= U[ n ] ) {
|
31 |
+
|
32 |
+
return n - 1;
|
33 |
+
|
34 |
+
}
|
35 |
+
|
36 |
+
if ( u <= U[ p ] ) {
|
37 |
+
|
38 |
+
return p;
|
39 |
+
|
40 |
+
}
|
41 |
+
|
42 |
+
let low = p;
|
43 |
+
let high = n;
|
44 |
+
let mid = Math.floor( ( low + high ) / 2 );
|
45 |
+
|
46 |
+
while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
|
47 |
+
|
48 |
+
if ( u < U[ mid ] ) {
|
49 |
+
|
50 |
+
high = mid;
|
51 |
+
|
52 |
+
} else {
|
53 |
+
|
54 |
+
low = mid;
|
55 |
+
|
56 |
+
}
|
57 |
+
|
58 |
+
mid = Math.floor( ( low + high ) / 2 );
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
return mid;
|
63 |
+
|
64 |
+
}
|
65 |
+
|
66 |
+
|
67 |
+
/*
|
68 |
+
Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
|
69 |
+
|
70 |
+
span : span in which u lies
|
71 |
+
u : parametric point
|
72 |
+
p : degree
|
73 |
+
U : knot vector
|
74 |
+
|
75 |
+
returns array[p+1] with basis functions values.
|
76 |
+
*/
|
77 |
+
function calcBasisFunctions( span, u, p, U ) {
|
78 |
+
|
79 |
+
const N = [];
|
80 |
+
const left = [];
|
81 |
+
const right = [];
|
82 |
+
N[ 0 ] = 1.0;
|
83 |
+
|
84 |
+
for ( let j = 1; j <= p; ++ j ) {
|
85 |
+
|
86 |
+
left[ j ] = u - U[ span + 1 - j ];
|
87 |
+
right[ j ] = U[ span + j ] - u;
|
88 |
+
|
89 |
+
let saved = 0.0;
|
90 |
+
|
91 |
+
for ( let r = 0; r < j; ++ r ) {
|
92 |
+
|
93 |
+
const rv = right[ r + 1 ];
|
94 |
+
const lv = left[ j - r ];
|
95 |
+
const temp = N[ r ] / ( rv + lv );
|
96 |
+
N[ r ] = saved + rv * temp;
|
97 |
+
saved = lv * temp;
|
98 |
+
|
99 |
+
}
|
100 |
+
|
101 |
+
N[ j ] = saved;
|
102 |
+
|
103 |
+
}
|
104 |
+
|
105 |
+
return N;
|
106 |
+
|
107 |
+
}
|
108 |
+
|
109 |
+
|
110 |
+
/*
|
111 |
+
Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
|
112 |
+
|
113 |
+
p : degree of B-Spline
|
114 |
+
U : knot vector
|
115 |
+
P : control points (x, y, z, w)
|
116 |
+
u : parametric point
|
117 |
+
|
118 |
+
returns point for given u
|
119 |
+
*/
|
120 |
+
function calcBSplinePoint( p, U, P, u ) {
|
121 |
+
|
122 |
+
const span = findSpan( p, u, U );
|
123 |
+
const N = calcBasisFunctions( span, u, p, U );
|
124 |
+
const C = new Vector4( 0, 0, 0, 0 );
|
125 |
+
|
126 |
+
for ( let j = 0; j <= p; ++ j ) {
|
127 |
+
|
128 |
+
const point = P[ span - p + j ];
|
129 |
+
const Nj = N[ j ];
|
130 |
+
const wNj = point.w * Nj;
|
131 |
+
C.x += point.x * wNj;
|
132 |
+
C.y += point.y * wNj;
|
133 |
+
C.z += point.z * wNj;
|
134 |
+
C.w += point.w * Nj;
|
135 |
+
|
136 |
+
}
|
137 |
+
|
138 |
+
return C;
|
139 |
+
|
140 |
+
}
|
141 |
+
|
142 |
+
|
143 |
+
/*
|
144 |
+
Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
|
145 |
+
|
146 |
+
span : span in which u lies
|
147 |
+
u : parametric point
|
148 |
+
p : degree
|
149 |
+
n : number of derivatives to calculate
|
150 |
+
U : knot vector
|
151 |
+
|
152 |
+
returns array[n+1][p+1] with basis functions derivatives
|
153 |
+
*/
|
154 |
+
function calcBasisFunctionDerivatives( span, u, p, n, U ) {
|
155 |
+
|
156 |
+
const zeroArr = [];
|
157 |
+
for ( let i = 0; i <= p; ++ i )
|
158 |
+
zeroArr[ i ] = 0.0;
|
159 |
+
|
160 |
+
const ders = [];
|
161 |
+
|
162 |
+
for ( let i = 0; i <= n; ++ i )
|
163 |
+
ders[ i ] = zeroArr.slice( 0 );
|
164 |
+
|
165 |
+
const ndu = [];
|
166 |
+
|
167 |
+
for ( let i = 0; i <= p; ++ i )
|
168 |
+
ndu[ i ] = zeroArr.slice( 0 );
|
169 |
+
|
170 |
+
ndu[ 0 ][ 0 ] = 1.0;
|
171 |
+
|
172 |
+
const left = zeroArr.slice( 0 );
|
173 |
+
const right = zeroArr.slice( 0 );
|
174 |
+
|
175 |
+
for ( let j = 1; j <= p; ++ j ) {
|
176 |
+
|
177 |
+
left[ j ] = u - U[ span + 1 - j ];
|
178 |
+
right[ j ] = U[ span + j ] - u;
|
179 |
+
|
180 |
+
let saved = 0.0;
|
181 |
+
|
182 |
+
for ( let r = 0; r < j; ++ r ) {
|
183 |
+
|
184 |
+
const rv = right[ r + 1 ];
|
185 |
+
const lv = left[ j - r ];
|
186 |
+
ndu[ j ][ r ] = rv + lv;
|
187 |
+
|
188 |
+
const temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
|
189 |
+
ndu[ r ][ j ] = saved + rv * temp;
|
190 |
+
saved = lv * temp;
|
191 |
+
|
192 |
+
}
|
193 |
+
|
194 |
+
ndu[ j ][ j ] = saved;
|
195 |
+
|
196 |
+
}
|
197 |
+
|
198 |
+
for ( let j = 0; j <= p; ++ j ) {
|
199 |
+
|
200 |
+
ders[ 0 ][ j ] = ndu[ j ][ p ];
|
201 |
+
|
202 |
+
}
|
203 |
+
|
204 |
+
for ( let r = 0; r <= p; ++ r ) {
|
205 |
+
|
206 |
+
let s1 = 0;
|
207 |
+
let s2 = 1;
|
208 |
+
|
209 |
+
const a = [];
|
210 |
+
for ( let i = 0; i <= p; ++ i ) {
|
211 |
+
|
212 |
+
a[ i ] = zeroArr.slice( 0 );
|
213 |
+
|
214 |
+
}
|
215 |
+
|
216 |
+
a[ 0 ][ 0 ] = 1.0;
|
217 |
+
|
218 |
+
for ( let k = 1; k <= n; ++ k ) {
|
219 |
+
|
220 |
+
let d = 0.0;
|
221 |
+
const rk = r - k;
|
222 |
+
const pk = p - k;
|
223 |
+
|
224 |
+
if ( r >= k ) {
|
225 |
+
|
226 |
+
a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
|
227 |
+
d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
|
228 |
+
|
229 |
+
}
|
230 |
+
|
231 |
+
const j1 = ( rk >= - 1 ) ? 1 : - rk;
|
232 |
+
const j2 = ( r - 1 <= pk ) ? k - 1 : p - r;
|
233 |
+
|
234 |
+
for ( let j = j1; j <= j2; ++ j ) {
|
235 |
+
|
236 |
+
a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
|
237 |
+
d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
|
238 |
+
|
239 |
+
}
|
240 |
+
|
241 |
+
if ( r <= pk ) {
|
242 |
+
|
243 |
+
a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
|
244 |
+
d += a[ s2 ][ k ] * ndu[ r ][ pk ];
|
245 |
+
|
246 |
+
}
|
247 |
+
|
248 |
+
ders[ k ][ r ] = d;
|
249 |
+
|
250 |
+
const j = s1;
|
251 |
+
s1 = s2;
|
252 |
+
s2 = j;
|
253 |
+
|
254 |
+
}
|
255 |
+
|
256 |
+
}
|
257 |
+
|
258 |
+
let r = p;
|
259 |
+
|
260 |
+
for ( let k = 1; k <= n; ++ k ) {
|
261 |
+
|
262 |
+
for ( let j = 0; j <= p; ++ j ) {
|
263 |
+
|
264 |
+
ders[ k ][ j ] *= r;
|
265 |
+
|
266 |
+
}
|
267 |
+
|
268 |
+
r *= p - k;
|
269 |
+
|
270 |
+
}
|
271 |
+
|
272 |
+
return ders;
|
273 |
+
|
274 |
+
}
|
275 |
+
|
276 |
+
|
277 |
+
/*
|
278 |
+
Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
|
279 |
+
|
280 |
+
p : degree
|
281 |
+
U : knot vector
|
282 |
+
P : control points
|
283 |
+
u : Parametric points
|
284 |
+
nd : number of derivatives
|
285 |
+
|
286 |
+
returns array[d+1] with derivatives
|
287 |
+
*/
|
288 |
+
function calcBSplineDerivatives( p, U, P, u, nd ) {
|
289 |
+
|
290 |
+
const du = nd < p ? nd : p;
|
291 |
+
const CK = [];
|
292 |
+
const span = findSpan( p, u, U );
|
293 |
+
const nders = calcBasisFunctionDerivatives( span, u, p, du, U );
|
294 |
+
const Pw = [];
|
295 |
+
|
296 |
+
for ( let i = 0; i < P.length; ++ i ) {
|
297 |
+
|
298 |
+
const point = P[ i ].clone();
|
299 |
+
const w = point.w;
|
300 |
+
|
301 |
+
point.x *= w;
|
302 |
+
point.y *= w;
|
303 |
+
point.z *= w;
|
304 |
+
|
305 |
+
Pw[ i ] = point;
|
306 |
+
|
307 |
+
}
|
308 |
+
|
309 |
+
for ( let k = 0; k <= du; ++ k ) {
|
310 |
+
|
311 |
+
const point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
|
312 |
+
|
313 |
+
for ( let j = 1; j <= p; ++ j ) {
|
314 |
+
|
315 |
+
point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
|
316 |
+
|
317 |
+
}
|
318 |
+
|
319 |
+
CK[ k ] = point;
|
320 |
+
|
321 |
+
}
|
322 |
+
|
323 |
+
for ( let k = du + 1; k <= nd + 1; ++ k ) {
|
324 |
+
|
325 |
+
CK[ k ] = new Vector4( 0, 0, 0 );
|
326 |
+
|
327 |
+
}
|
328 |
+
|
329 |
+
return CK;
|
330 |
+
|
331 |
+
}
|
332 |
+
|
333 |
+
|
334 |
+
/*
|
335 |
+
Calculate "K over I"
|
336 |
+
|
337 |
+
returns k!/(i!(k-i)!)
|
338 |
+
*/
|
339 |
+
function calcKoverI( k, i ) {
|
340 |
+
|
341 |
+
let nom = 1;
|
342 |
+
|
343 |
+
for ( let j = 2; j <= k; ++ j ) {
|
344 |
+
|
345 |
+
nom *= j;
|
346 |
+
|
347 |
+
}
|
348 |
+
|
349 |
+
let denom = 1;
|
350 |
+
|
351 |
+
for ( let j = 2; j <= i; ++ j ) {
|
352 |
+
|
353 |
+
denom *= j;
|
354 |
+
|
355 |
+
}
|
356 |
+
|
357 |
+
for ( let j = 2; j <= k - i; ++ j ) {
|
358 |
+
|
359 |
+
denom *= j;
|
360 |
+
|
361 |
+
}
|
362 |
+
|
363 |
+
return nom / denom;
|
364 |
+
|
365 |
+
}
|
366 |
+
|
367 |
+
|
368 |
+
/*
|
369 |
+
Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
|
370 |
+
|
371 |
+
Pders : result of function calcBSplineDerivatives
|
372 |
+
|
373 |
+
returns array with derivatives for rational curve.
|
374 |
+
*/
|
375 |
+
function calcRationalCurveDerivatives( Pders ) {
|
376 |
+
|
377 |
+
const nd = Pders.length;
|
378 |
+
const Aders = [];
|
379 |
+
const wders = [];
|
380 |
+
|
381 |
+
for ( let i = 0; i < nd; ++ i ) {
|
382 |
+
|
383 |
+
const point = Pders[ i ];
|
384 |
+
Aders[ i ] = new Vector3( point.x, point.y, point.z );
|
385 |
+
wders[ i ] = point.w;
|
386 |
+
|
387 |
+
}
|
388 |
+
|
389 |
+
const CK = [];
|
390 |
+
|
391 |
+
for ( let k = 0; k < nd; ++ k ) {
|
392 |
+
|
393 |
+
const v = Aders[ k ].clone();
|
394 |
+
|
395 |
+
for ( let i = 1; i <= k; ++ i ) {
|
396 |
+
|
397 |
+
v.sub( CK[ k - i ].clone().multiplyScalar( calcKoverI( k, i ) * wders[ i ] ) );
|
398 |
+
|
399 |
+
}
|
400 |
+
|
401 |
+
CK[ k ] = v.divideScalar( wders[ 0 ] );
|
402 |
+
|
403 |
+
}
|
404 |
+
|
405 |
+
return CK;
|
406 |
+
|
407 |
+
}
|
408 |
+
|
409 |
+
|
410 |
+
/*
|
411 |
+
Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
|
412 |
+
|
413 |
+
p : degree
|
414 |
+
U : knot vector
|
415 |
+
P : control points in homogeneous space
|
416 |
+
u : parametric points
|
417 |
+
nd : number of derivatives
|
418 |
+
|
419 |
+
returns array with derivatives.
|
420 |
+
*/
|
421 |
+
function calcNURBSDerivatives( p, U, P, u, nd ) {
|
422 |
+
|
423 |
+
const Pders = calcBSplineDerivatives( p, U, P, u, nd );
|
424 |
+
return calcRationalCurveDerivatives( Pders );
|
425 |
+
|
426 |
+
}
|
427 |
+
|
428 |
+
|
429 |
+
/*
|
430 |
+
Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
|
431 |
+
|
432 |
+
p, q : degrees of B-Spline surface
|
433 |
+
U, V : knot vectors
|
434 |
+
P : control points (x, y, z, w)
|
435 |
+
u, v : parametric values
|
436 |
+
|
437 |
+
returns point for given (u, v)
|
438 |
+
*/
|
439 |
+
function calcSurfacePoint( p, q, U, V, P, u, v, target ) {
|
440 |
+
|
441 |
+
const uspan = findSpan( p, u, U );
|
442 |
+
const vspan = findSpan( q, v, V );
|
443 |
+
const Nu = calcBasisFunctions( uspan, u, p, U );
|
444 |
+
const Nv = calcBasisFunctions( vspan, v, q, V );
|
445 |
+
const temp = [];
|
446 |
+
|
447 |
+
for ( let l = 0; l <= q; ++ l ) {
|
448 |
+
|
449 |
+
temp[ l ] = new Vector4( 0, 0, 0, 0 );
|
450 |
+
for ( let k = 0; k <= p; ++ k ) {
|
451 |
+
|
452 |
+
const point = P[ uspan - p + k ][ vspan - q + l ].clone();
|
453 |
+
const w = point.w;
|
454 |
+
point.x *= w;
|
455 |
+
point.y *= w;
|
456 |
+
point.z *= w;
|
457 |
+
temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
|
458 |
+
|
459 |
+
}
|
460 |
+
|
461 |
+
}
|
462 |
+
|
463 |
+
const Sw = new Vector4( 0, 0, 0, 0 );
|
464 |
+
for ( let l = 0; l <= q; ++ l ) {
|
465 |
+
|
466 |
+
Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
|
467 |
+
|
468 |
+
}
|
469 |
+
|
470 |
+
Sw.divideScalar( Sw.w );
|
471 |
+
target.set( Sw.x, Sw.y, Sw.z );
|
472 |
+
|
473 |
+
}
|
474 |
+
|
475 |
+
/*
|
476 |
+
Calculate rational B-Spline volume point. See The NURBS Book, page 134, algorithm A4.3.
|
477 |
+
|
478 |
+
p, q, r : degrees of B-Splinevolume
|
479 |
+
U, V, W : knot vectors
|
480 |
+
P : control points (x, y, z, w)
|
481 |
+
u, v, w : parametric values
|
482 |
+
|
483 |
+
returns point for given (u, v, w)
|
484 |
+
*/
|
485 |
+
function calcVolumePoint( p, q, r, U, V, W, P, u, v, w, target ) {
|
486 |
+
|
487 |
+
const uspan = findSpan( p, u, U );
|
488 |
+
const vspan = findSpan( q, v, V );
|
489 |
+
const wspan = findSpan( r, w, W );
|
490 |
+
const Nu = calcBasisFunctions( uspan, u, p, U );
|
491 |
+
const Nv = calcBasisFunctions( vspan, v, q, V );
|
492 |
+
const Nw = calcBasisFunctions( wspan, w, r, W );
|
493 |
+
const temp = [];
|
494 |
+
|
495 |
+
for ( let m = 0; m <= r; ++ m ) {
|
496 |
+
|
497 |
+
temp[ m ] = [];
|
498 |
+
|
499 |
+
for ( let l = 0; l <= q; ++ l ) {
|
500 |
+
|
501 |
+
temp[ m ][ l ] = new Vector4( 0, 0, 0, 0 );
|
502 |
+
for ( let k = 0; k <= p; ++ k ) {
|
503 |
+
|
504 |
+
const point = P[ uspan - p + k ][ vspan - q + l ][ wspan - r + m ].clone();
|
505 |
+
const w = point.w;
|
506 |
+
point.x *= w;
|
507 |
+
point.y *= w;
|
508 |
+
point.z *= w;
|
509 |
+
temp[ m ][ l ].add( point.multiplyScalar( Nu[ k ] ) );
|
510 |
+
|
511 |
+
}
|
512 |
+
|
513 |
+
}
|
514 |
+
|
515 |
+
}
|
516 |
+
|
517 |
+
const Sw = new Vector4( 0, 0, 0, 0 );
|
518 |
+
for ( let m = 0; m <= r; ++ m ) {
|
519 |
+
|
520 |
+
for ( let l = 0; l <= q; ++ l ) {
|
521 |
+
|
522 |
+
Sw.add( temp[ m ][ l ].multiplyScalar( Nw[ m ] ).multiplyScalar( Nv[ l ] ) );
|
523 |
+
|
524 |
+
}
|
525 |
+
|
526 |
+
}
|
527 |
+
|
528 |
+
Sw.divideScalar( Sw.w );
|
529 |
+
target.set( Sw.x, Sw.y, Sw.z );
|
530 |
+
|
531 |
+
}
|
532 |
+
|
533 |
+
|
534 |
+
export {
|
535 |
+
findSpan,
|
536 |
+
calcBasisFunctions,
|
537 |
+
calcBSplinePoint,
|
538 |
+
calcBasisFunctionDerivatives,
|
539 |
+
calcBSplineDerivatives,
|
540 |
+
calcKoverI,
|
541 |
+
calcRationalCurveDerivatives,
|
542 |
+
calcNURBSDerivatives,
|
543 |
+
calcSurfacePoint,
|
544 |
+
calcVolumePoint,
|
545 |
+
};
|
libs/three.js/0.172.0/jsm/curves/NURBSVolume.js
ADDED
@@ -0,0 +1,62 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Vector4
|
3 |
+
} from 'three';
|
4 |
+
import * as NURBSUtils from '../curves/NURBSUtils.js';
|
5 |
+
|
6 |
+
/**
|
7 |
+
* NURBS volume object
|
8 |
+
*
|
9 |
+
* Implementation is based on (x, y, z [, w=1]]) control points with w=weight.
|
10 |
+
**/
|
11 |
+
|
12 |
+
class NURBSVolume {
|
13 |
+
|
14 |
+
constructor( degree1, degree2, degree3, knots1, knots2, knots3 /* arrays of reals */, controlPoints /* array^3 of Vector(2|3|4) */ ) {
|
15 |
+
|
16 |
+
this.degree1 = degree1;
|
17 |
+
this.degree2 = degree2;
|
18 |
+
this.degree3 = degree3;
|
19 |
+
this.knots1 = knots1;
|
20 |
+
this.knots2 = knots2;
|
21 |
+
this.knots3 = knots3;
|
22 |
+
this.controlPoints = [];
|
23 |
+
|
24 |
+
const len1 = knots1.length - degree1 - 1;
|
25 |
+
const len2 = knots2.length - degree2 - 1;
|
26 |
+
const len3 = knots3.length - degree3 - 1;
|
27 |
+
|
28 |
+
// ensure Vector4 for control points
|
29 |
+
for ( let i = 0; i < len1; ++ i ) {
|
30 |
+
|
31 |
+
this.controlPoints[ i ] = [];
|
32 |
+
|
33 |
+
for ( let j = 0; j < len2; ++ j ) {
|
34 |
+
|
35 |
+
this.controlPoints[ i ][ j ] = [];
|
36 |
+
|
37 |
+
for ( let k = 0; k < len3; ++ k ) {
|
38 |
+
|
39 |
+
const point = controlPoints[ i ][ j ][ k ];
|
40 |
+
this.controlPoints[ i ][ j ][ k ] = new Vector4( point.x, point.y, point.z, point.w );
|
41 |
+
|
42 |
+
}
|
43 |
+
|
44 |
+
}
|
45 |
+
|
46 |
+
}
|
47 |
+
|
48 |
+
}
|
49 |
+
|
50 |
+
getPoint( t1, t2, t3, target ) {
|
51 |
+
|
52 |
+
const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
|
53 |
+
const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->v
|
54 |
+
const w = this.knots3[ 0 ] + t3 * ( this.knots3[ this.knots3.length - 1 ] - this.knots3[ 0 ] ); // linear mapping t3->w
|
55 |
+
|
56 |
+
NURBSUtils.calcVolumePoint( this.degree1, this.degree2, this.degree3, this.knots1, this.knots2, this.knots3, this.controlPoints, u, v, w, target );
|
57 |
+
|
58 |
+
}
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
export { NURBSVolume };
|
libs/three.js/0.172.0/jsm/effects/AnaglyphEffect.js
ADDED
@@ -0,0 +1,147 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
LinearFilter,
|
3 |
+
Matrix3,
|
4 |
+
NearestFilter,
|
5 |
+
RGBAFormat,
|
6 |
+
ShaderMaterial,
|
7 |
+
StereoCamera,
|
8 |
+
WebGLRenderTarget
|
9 |
+
} from 'three';
|
10 |
+
import { FullScreenQuad } from '../postprocessing/Pass.js';
|
11 |
+
|
12 |
+
class AnaglyphEffect {
|
13 |
+
|
14 |
+
constructor( renderer, width = 512, height = 512 ) {
|
15 |
+
|
16 |
+
// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
|
17 |
+
|
18 |
+
this.colorMatrixLeft = new Matrix3().fromArray( [
|
19 |
+
0.456100, - 0.0400822, - 0.0152161,
|
20 |
+
0.500484, - 0.0378246, - 0.0205971,
|
21 |
+
0.176381, - 0.0157589, - 0.00546856
|
22 |
+
] );
|
23 |
+
|
24 |
+
this.colorMatrixRight = new Matrix3().fromArray( [
|
25 |
+
- 0.0434706, 0.378476, - 0.0721527,
|
26 |
+
- 0.0879388, 0.73364, - 0.112961,
|
27 |
+
- 0.00155529, - 0.0184503, 1.2264
|
28 |
+
] );
|
29 |
+
|
30 |
+
const _stereo = new StereoCamera();
|
31 |
+
|
32 |
+
const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
|
33 |
+
|
34 |
+
const _renderTargetL = new WebGLRenderTarget( width, height, _params );
|
35 |
+
const _renderTargetR = new WebGLRenderTarget( width, height, _params );
|
36 |
+
|
37 |
+
const _material = new ShaderMaterial( {
|
38 |
+
|
39 |
+
uniforms: {
|
40 |
+
|
41 |
+
'mapLeft': { value: _renderTargetL.texture },
|
42 |
+
'mapRight': { value: _renderTargetR.texture },
|
43 |
+
|
44 |
+
'colorMatrixLeft': { value: this.colorMatrixLeft },
|
45 |
+
'colorMatrixRight': { value: this.colorMatrixRight }
|
46 |
+
|
47 |
+
},
|
48 |
+
|
49 |
+
vertexShader: [
|
50 |
+
|
51 |
+
'varying vec2 vUv;',
|
52 |
+
|
53 |
+
'void main() {',
|
54 |
+
|
55 |
+
' vUv = vec2( uv.x, uv.y );',
|
56 |
+
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
|
57 |
+
|
58 |
+
'}'
|
59 |
+
|
60 |
+
].join( '\n' ),
|
61 |
+
|
62 |
+
fragmentShader: [
|
63 |
+
|
64 |
+
'uniform sampler2D mapLeft;',
|
65 |
+
'uniform sampler2D mapRight;',
|
66 |
+
'varying vec2 vUv;',
|
67 |
+
|
68 |
+
'uniform mat3 colorMatrixLeft;',
|
69 |
+
'uniform mat3 colorMatrixRight;',
|
70 |
+
|
71 |
+
'void main() {',
|
72 |
+
|
73 |
+
' vec2 uv = vUv;',
|
74 |
+
|
75 |
+
' vec4 colorL = texture2D( mapLeft, uv );',
|
76 |
+
' vec4 colorR = texture2D( mapRight, uv );',
|
77 |
+
|
78 |
+
' vec3 color = clamp(',
|
79 |
+
' colorMatrixLeft * colorL.rgb +',
|
80 |
+
' colorMatrixRight * colorR.rgb, 0., 1. );',
|
81 |
+
|
82 |
+
' gl_FragColor = vec4(',
|
83 |
+
' color.r, color.g, color.b,',
|
84 |
+
' max( colorL.a, colorR.a ) );',
|
85 |
+
|
86 |
+
' #include <tonemapping_fragment>',
|
87 |
+
' #include <colorspace_fragment>',
|
88 |
+
|
89 |
+
'}'
|
90 |
+
|
91 |
+
].join( '\n' )
|
92 |
+
|
93 |
+
} );
|
94 |
+
|
95 |
+
const _quad = new FullScreenQuad( _material );
|
96 |
+
|
97 |
+
this.setSize = function ( width, height ) {
|
98 |
+
|
99 |
+
renderer.setSize( width, height );
|
100 |
+
|
101 |
+
const pixelRatio = renderer.getPixelRatio();
|
102 |
+
|
103 |
+
_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
|
104 |
+
_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
|
105 |
+
|
106 |
+
};
|
107 |
+
|
108 |
+
this.render = function ( scene, camera ) {
|
109 |
+
|
110 |
+
const currentRenderTarget = renderer.getRenderTarget();
|
111 |
+
|
112 |
+
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
|
113 |
+
|
114 |
+
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
|
115 |
+
|
116 |
+
_stereo.update( camera );
|
117 |
+
|
118 |
+
renderer.setRenderTarget( _renderTargetL );
|
119 |
+
renderer.clear();
|
120 |
+
renderer.render( scene, _stereo.cameraL );
|
121 |
+
|
122 |
+
renderer.setRenderTarget( _renderTargetR );
|
123 |
+
renderer.clear();
|
124 |
+
renderer.render( scene, _stereo.cameraR );
|
125 |
+
|
126 |
+
renderer.setRenderTarget( null );
|
127 |
+
_quad.render( renderer );
|
128 |
+
|
129 |
+
renderer.setRenderTarget( currentRenderTarget );
|
130 |
+
|
131 |
+
};
|
132 |
+
|
133 |
+
this.dispose = function () {
|
134 |
+
|
135 |
+
_renderTargetL.dispose();
|
136 |
+
_renderTargetR.dispose();
|
137 |
+
|
138 |
+
_material.dispose();
|
139 |
+
_quad.dispose();
|
140 |
+
|
141 |
+
};
|
142 |
+
|
143 |
+
}
|
144 |
+
|
145 |
+
}
|
146 |
+
|
147 |
+
export { AnaglyphEffect };
|
libs/three.js/0.172.0/jsm/effects/AsciiEffect.js
ADDED
@@ -0,0 +1,263 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Ascii generation is based on https://github.com/hassadee/jsascii/blob/master/jsascii.js
|
3 |
+
*
|
4 |
+
* 16 April 2012 - @blurspline
|
5 |
+
*/
|
6 |
+
|
7 |
+
class AsciiEffect {
|
8 |
+
|
9 |
+
constructor( renderer, charSet = ' .:-=+*#%@', options = {} ) {
|
10 |
+
|
11 |
+
// ' .,:;=|iI+hHOE#`$';
|
12 |
+
// darker bolder character set from https://github.com/saw/Canvas-ASCII-Art/
|
13 |
+
// ' .\'`^",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$'.split('');
|
14 |
+
|
15 |
+
// Some ASCII settings
|
16 |
+
|
17 |
+
const fResolution = options[ 'resolution' ] || 0.15; // Higher for more details
|
18 |
+
const iScale = options[ 'scale' ] || 1;
|
19 |
+
const bColor = options[ 'color' ] || false; // nice but slows down rendering!
|
20 |
+
const bAlpha = options[ 'alpha' ] || false; // Transparency
|
21 |
+
const bBlock = options[ 'block' ] || false; // blocked characters. like good O dos
|
22 |
+
const bInvert = options[ 'invert' ] || false; // black is white, white is black
|
23 |
+
const strResolution = options[ 'strResolution' ] || 'low';
|
24 |
+
|
25 |
+
let width, height;
|
26 |
+
|
27 |
+
const domElement = document.createElement( 'div' );
|
28 |
+
domElement.style.cursor = 'default';
|
29 |
+
|
30 |
+
const oAscii = document.createElement( 'table' );
|
31 |
+
domElement.appendChild( oAscii );
|
32 |
+
|
33 |
+
let iWidth, iHeight;
|
34 |
+
let oImg;
|
35 |
+
|
36 |
+
this.setSize = function ( w, h ) {
|
37 |
+
|
38 |
+
width = w;
|
39 |
+
height = h;
|
40 |
+
|
41 |
+
renderer.setSize( w, h );
|
42 |
+
|
43 |
+
initAsciiSize();
|
44 |
+
|
45 |
+
};
|
46 |
+
|
47 |
+
|
48 |
+
this.render = function ( scene, camera ) {
|
49 |
+
|
50 |
+
renderer.render( scene, camera );
|
51 |
+
asciifyImage( oAscii );
|
52 |
+
|
53 |
+
};
|
54 |
+
|
55 |
+
this.domElement = domElement;
|
56 |
+
|
57 |
+
|
58 |
+
// Throw in ascii library from https://github.com/hassadee/jsascii/blob/master/jsascii.js (MIT License)
|
59 |
+
|
60 |
+
function initAsciiSize() {
|
61 |
+
|
62 |
+
iWidth = Math.floor( width * fResolution );
|
63 |
+
iHeight = Math.floor( height * fResolution );
|
64 |
+
|
65 |
+
oCanvas.width = iWidth;
|
66 |
+
oCanvas.height = iHeight;
|
67 |
+
// oCanvas.style.display = "none";
|
68 |
+
// oCanvas.style.width = iWidth;
|
69 |
+
// oCanvas.style.height = iHeight;
|
70 |
+
|
71 |
+
oImg = renderer.domElement;
|
72 |
+
|
73 |
+
if ( oImg.style.backgroundColor ) {
|
74 |
+
|
75 |
+
oAscii.rows[ 0 ].cells[ 0 ].style.backgroundColor = oImg.style.backgroundColor;
|
76 |
+
oAscii.rows[ 0 ].cells[ 0 ].style.color = oImg.style.color;
|
77 |
+
|
78 |
+
}
|
79 |
+
|
80 |
+
oAscii.cellSpacing = 0;
|
81 |
+
oAscii.cellPadding = 0;
|
82 |
+
|
83 |
+
const oStyle = oAscii.style;
|
84 |
+
oStyle.whiteSpace = 'pre';
|
85 |
+
oStyle.margin = '0px';
|
86 |
+
oStyle.padding = '0px';
|
87 |
+
oStyle.letterSpacing = fLetterSpacing + 'px';
|
88 |
+
oStyle.fontFamily = strFont;
|
89 |
+
oStyle.fontSize = fFontSize + 'px';
|
90 |
+
oStyle.lineHeight = fLineHeight + 'px';
|
91 |
+
oStyle.textAlign = 'left';
|
92 |
+
oStyle.textDecoration = 'none';
|
93 |
+
|
94 |
+
}
|
95 |
+
|
96 |
+
|
97 |
+
const aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' );
|
98 |
+
const aDefaultColorCharList = ( ' CGO08@' ).split( '' );
|
99 |
+
const strFont = 'courier new, monospace';
|
100 |
+
|
101 |
+
const oCanvasImg = renderer.domElement;
|
102 |
+
|
103 |
+
const oCanvas = document.createElement( 'canvas' );
|
104 |
+
if ( ! oCanvas.getContext ) {
|
105 |
+
|
106 |
+
return;
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
const oCtx = oCanvas.getContext( '2d' );
|
111 |
+
if ( ! oCtx.getImageData ) {
|
112 |
+
|
113 |
+
return;
|
114 |
+
|
115 |
+
}
|
116 |
+
|
117 |
+
let aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList );
|
118 |
+
|
119 |
+
if ( charSet ) aCharList = charSet;
|
120 |
+
|
121 |
+
// Setup dom
|
122 |
+
|
123 |
+
const fFontSize = ( 2 / fResolution ) * iScale;
|
124 |
+
const fLineHeight = ( 2 / fResolution ) * iScale;
|
125 |
+
|
126 |
+
// adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
|
127 |
+
|
128 |
+
let fLetterSpacing = 0;
|
129 |
+
|
130 |
+
if ( strResolution == 'low' ) {
|
131 |
+
|
132 |
+
switch ( iScale ) {
|
133 |
+
|
134 |
+
case 1 : fLetterSpacing = - 1; break;
|
135 |
+
case 2 :
|
136 |
+
case 3 : fLetterSpacing = - 2.1; break;
|
137 |
+
case 4 : fLetterSpacing = - 3.1; break;
|
138 |
+
case 5 : fLetterSpacing = - 4.15; break;
|
139 |
+
|
140 |
+
}
|
141 |
+
|
142 |
+
}
|
143 |
+
|
144 |
+
if ( strResolution == 'medium' ) {
|
145 |
+
|
146 |
+
switch ( iScale ) {
|
147 |
+
|
148 |
+
case 1 : fLetterSpacing = 0; break;
|
149 |
+
case 2 : fLetterSpacing = - 1; break;
|
150 |
+
case 3 : fLetterSpacing = - 1.04; break;
|
151 |
+
case 4 :
|
152 |
+
case 5 : fLetterSpacing = - 2.1; break;
|
153 |
+
|
154 |
+
}
|
155 |
+
|
156 |
+
}
|
157 |
+
|
158 |
+
if ( strResolution == 'high' ) {
|
159 |
+
|
160 |
+
switch ( iScale ) {
|
161 |
+
|
162 |
+
case 1 :
|
163 |
+
case 2 : fLetterSpacing = 0; break;
|
164 |
+
case 3 :
|
165 |
+
case 4 :
|
166 |
+
case 5 : fLetterSpacing = - 1; break;
|
167 |
+
|
168 |
+
}
|
169 |
+
|
170 |
+
}
|
171 |
+
|
172 |
+
|
173 |
+
// can't get a span or div to flow like an img element, but a table works?
|
174 |
+
|
175 |
+
|
176 |
+
// convert img element to ascii
|
177 |
+
|
178 |
+
function asciifyImage( oAscii ) {
|
179 |
+
|
180 |
+
oCtx.clearRect( 0, 0, iWidth, iHeight );
|
181 |
+
oCtx.drawImage( oCanvasImg, 0, 0, iWidth, iHeight );
|
182 |
+
const oImgData = oCtx.getImageData( 0, 0, iWidth, iHeight ).data;
|
183 |
+
|
184 |
+
// Coloring loop starts now
|
185 |
+
let strChars = '';
|
186 |
+
|
187 |
+
// console.time('rendering');
|
188 |
+
|
189 |
+
for ( let y = 0; y < iHeight; y += 2 ) {
|
190 |
+
|
191 |
+
for ( let x = 0; x < iWidth; x ++ ) {
|
192 |
+
|
193 |
+
const iOffset = ( y * iWidth + x ) * 4;
|
194 |
+
|
195 |
+
const iRed = oImgData[ iOffset ];
|
196 |
+
const iGreen = oImgData[ iOffset + 1 ];
|
197 |
+
const iBlue = oImgData[ iOffset + 2 ];
|
198 |
+
const iAlpha = oImgData[ iOffset + 3 ];
|
199 |
+
let iCharIdx;
|
200 |
+
|
201 |
+
let fBrightness;
|
202 |
+
|
203 |
+
fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255;
|
204 |
+
// fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
|
205 |
+
|
206 |
+
if ( iAlpha == 0 ) {
|
207 |
+
|
208 |
+
// should calculate alpha instead, but quick hack :)
|
209 |
+
//fBrightness *= (iAlpha / 255);
|
210 |
+
fBrightness = 1;
|
211 |
+
|
212 |
+
}
|
213 |
+
|
214 |
+
iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
|
215 |
+
|
216 |
+
if ( bInvert ) {
|
217 |
+
|
218 |
+
iCharIdx = aCharList.length - iCharIdx - 1;
|
219 |
+
|
220 |
+
}
|
221 |
+
|
222 |
+
// good for debugging
|
223 |
+
//fBrightness = Math.floor(fBrightness * 10);
|
224 |
+
//strThisChar = fBrightness;
|
225 |
+
|
226 |
+
let strThisChar = aCharList[ iCharIdx ];
|
227 |
+
|
228 |
+
if ( strThisChar === undefined || strThisChar == ' ' )
|
229 |
+
strThisChar = ' ';
|
230 |
+
|
231 |
+
if ( bColor ) {
|
232 |
+
|
233 |
+
strChars += '<span style=\''
|
234 |
+
+ 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');'
|
235 |
+
+ ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' )
|
236 |
+
+ ( bAlpha ? 'opacity:' + ( iAlpha / 255 ) + ';' : '' )
|
237 |
+
+ '\'>' + strThisChar + '</span>';
|
238 |
+
|
239 |
+
} else {
|
240 |
+
|
241 |
+
strChars += strThisChar;
|
242 |
+
|
243 |
+
}
|
244 |
+
|
245 |
+
}
|
246 |
+
|
247 |
+
strChars += '<br/>';
|
248 |
+
|
249 |
+
}
|
250 |
+
|
251 |
+
oAscii.innerHTML = `<tr><td style="display:block;width:${width}px;height:${height}px;overflow:hidden">${strChars}</td></tr>`;
|
252 |
+
|
253 |
+
// console.timeEnd('rendering');
|
254 |
+
|
255 |
+
// return oAscii;
|
256 |
+
|
257 |
+
}
|
258 |
+
|
259 |
+
}
|
260 |
+
|
261 |
+
}
|
262 |
+
|
263 |
+
export { AsciiEffect };
|
libs/three.js/0.172.0/jsm/effects/OutlineEffect.js
ADDED
@@ -0,0 +1,539 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BackSide,
|
3 |
+
Color,
|
4 |
+
ShaderMaterial,
|
5 |
+
UniformsLib,
|
6 |
+
UniformsUtils
|
7 |
+
} from 'three';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Reference: https://en.wikipedia.org/wiki/Cel_shading
|
11 |
+
*
|
12 |
+
* API
|
13 |
+
*
|
14 |
+
* 1. Traditional
|
15 |
+
*
|
16 |
+
* const effect = new OutlineEffect( renderer );
|
17 |
+
*
|
18 |
+
* function render() {
|
19 |
+
*
|
20 |
+
* effect.render( scene, camera );
|
21 |
+
*
|
22 |
+
* }
|
23 |
+
*
|
24 |
+
* 2. VR compatible
|
25 |
+
*
|
26 |
+
* const effect = new OutlineEffect( renderer );
|
27 |
+
* let renderingOutline = false;
|
28 |
+
*
|
29 |
+
* scene.onAfterRender = function () {
|
30 |
+
*
|
31 |
+
* if ( renderingOutline ) return;
|
32 |
+
*
|
33 |
+
* renderingOutline = true;
|
34 |
+
*
|
35 |
+
* effect.renderOutline( scene, camera );
|
36 |
+
*
|
37 |
+
* renderingOutline = false;
|
38 |
+
*
|
39 |
+
* };
|
40 |
+
*
|
41 |
+
* function render() {
|
42 |
+
*
|
43 |
+
* renderer.render( scene, camera );
|
44 |
+
*
|
45 |
+
* }
|
46 |
+
*
|
47 |
+
* // How to set default outline parameters
|
48 |
+
* new OutlineEffect( renderer, {
|
49 |
+
* defaultThickness: 0.01,
|
50 |
+
* defaultColor: [ 0, 0, 0 ],
|
51 |
+
* defaultAlpha: 0.8,
|
52 |
+
* defaultKeepAlive: true // keeps outline material in cache even if material is removed from scene
|
53 |
+
* } );
|
54 |
+
*
|
55 |
+
* // How to set outline parameters for each material
|
56 |
+
* material.userData.outlineParameters = {
|
57 |
+
* thickness: 0.01,
|
58 |
+
* color: [ 0, 0, 0 ],
|
59 |
+
* alpha: 0.8,
|
60 |
+
* visible: true,
|
61 |
+
* keepAlive: true
|
62 |
+
* };
|
63 |
+
*/
|
64 |
+
|
65 |
+
class OutlineEffect {
|
66 |
+
|
67 |
+
constructor( renderer, parameters = {} ) {
|
68 |
+
|
69 |
+
this.enabled = true;
|
70 |
+
|
71 |
+
const defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
|
72 |
+
const defaultColor = new Color().fromArray( parameters.defaultColor !== undefined ? parameters.defaultColor : [ 0, 0, 0 ] );
|
73 |
+
const defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
|
74 |
+
const defaultKeepAlive = parameters.defaultKeepAlive !== undefined ? parameters.defaultKeepAlive : false;
|
75 |
+
|
76 |
+
// object.material.uuid -> outlineMaterial or
|
77 |
+
// object.material[ n ].uuid -> outlineMaterial
|
78 |
+
// save at the outline material creation and release
|
79 |
+
// if it's unused removeThresholdCount frames
|
80 |
+
// unless keepAlive is true.
|
81 |
+
const cache = {};
|
82 |
+
|
83 |
+
const removeThresholdCount = 60;
|
84 |
+
|
85 |
+
// outlineMaterial.uuid -> object.material or
|
86 |
+
// outlineMaterial.uuid -> object.material[ n ]
|
87 |
+
// save before render and release after render.
|
88 |
+
const originalMaterials = {};
|
89 |
+
|
90 |
+
// object.uuid -> originalOnBeforeRender
|
91 |
+
// save before render and release after render.
|
92 |
+
const originalOnBeforeRenders = {};
|
93 |
+
|
94 |
+
//this.cache = cache; // for debug
|
95 |
+
|
96 |
+
const uniformsOutline = {
|
97 |
+
outlineThickness: { value: defaultThickness },
|
98 |
+
outlineColor: { value: defaultColor },
|
99 |
+
outlineAlpha: { value: defaultAlpha }
|
100 |
+
};
|
101 |
+
|
102 |
+
const vertexShader = [
|
103 |
+
'#include <common>',
|
104 |
+
'#include <uv_pars_vertex>',
|
105 |
+
'#include <displacementmap_pars_vertex>',
|
106 |
+
'#include <fog_pars_vertex>',
|
107 |
+
'#include <morphtarget_pars_vertex>',
|
108 |
+
'#include <skinning_pars_vertex>',
|
109 |
+
'#include <logdepthbuf_pars_vertex>',
|
110 |
+
'#include <clipping_planes_pars_vertex>',
|
111 |
+
|
112 |
+
'uniform float outlineThickness;',
|
113 |
+
|
114 |
+
'vec4 calculateOutline( vec4 pos, vec3 normal, vec4 skinned ) {',
|
115 |
+
' float thickness = outlineThickness;',
|
116 |
+
' const float ratio = 1.0;', // TODO: support outline thickness ratio for each vertex
|
117 |
+
' vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + normal, 1.0 );',
|
118 |
+
// NOTE: subtract pos2 from pos because BackSide objectNormal is negative
|
119 |
+
' vec4 norm = normalize( pos - pos2 );',
|
120 |
+
' return pos + norm * thickness * pos.w * ratio;',
|
121 |
+
'}',
|
122 |
+
|
123 |
+
'void main() {',
|
124 |
+
|
125 |
+
' #include <uv_vertex>',
|
126 |
+
|
127 |
+
' #include <beginnormal_vertex>',
|
128 |
+
' #include <morphnormal_vertex>',
|
129 |
+
' #include <skinbase_vertex>',
|
130 |
+
' #include <skinnormal_vertex>',
|
131 |
+
|
132 |
+
' #include <begin_vertex>',
|
133 |
+
' #include <morphtarget_vertex>',
|
134 |
+
' #include <skinning_vertex>',
|
135 |
+
' #include <displacementmap_vertex>',
|
136 |
+
' #include <project_vertex>',
|
137 |
+
|
138 |
+
' vec3 outlineNormal = - objectNormal;', // the outline material is always rendered with BackSide
|
139 |
+
|
140 |
+
' gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );',
|
141 |
+
|
142 |
+
' #include <logdepthbuf_vertex>',
|
143 |
+
' #include <clipping_planes_vertex>',
|
144 |
+
' #include <fog_vertex>',
|
145 |
+
|
146 |
+
'}',
|
147 |
+
|
148 |
+
].join( '\n' );
|
149 |
+
|
150 |
+
const fragmentShader = [
|
151 |
+
|
152 |
+
'#include <common>',
|
153 |
+
'#include <fog_pars_fragment>',
|
154 |
+
'#include <logdepthbuf_pars_fragment>',
|
155 |
+
'#include <clipping_planes_pars_fragment>',
|
156 |
+
|
157 |
+
'uniform vec3 outlineColor;',
|
158 |
+
'uniform float outlineAlpha;',
|
159 |
+
|
160 |
+
'void main() {',
|
161 |
+
|
162 |
+
' #include <clipping_planes_fragment>',
|
163 |
+
' #include <logdepthbuf_fragment>',
|
164 |
+
|
165 |
+
' gl_FragColor = vec4( outlineColor, outlineAlpha );',
|
166 |
+
|
167 |
+
' #include <tonemapping_fragment>',
|
168 |
+
' #include <colorspace_fragment>',
|
169 |
+
' #include <fog_fragment>',
|
170 |
+
' #include <premultiplied_alpha_fragment>',
|
171 |
+
|
172 |
+
'}'
|
173 |
+
|
174 |
+
].join( '\n' );
|
175 |
+
|
176 |
+
function createMaterial() {
|
177 |
+
|
178 |
+
return new ShaderMaterial( {
|
179 |
+
type: 'OutlineEffect',
|
180 |
+
uniforms: UniformsUtils.merge( [
|
181 |
+
UniformsLib[ 'fog' ],
|
182 |
+
UniformsLib[ 'displacementmap' ],
|
183 |
+
uniformsOutline
|
184 |
+
] ),
|
185 |
+
vertexShader: vertexShader,
|
186 |
+
fragmentShader: fragmentShader,
|
187 |
+
side: BackSide
|
188 |
+
} );
|
189 |
+
|
190 |
+
}
|
191 |
+
|
192 |
+
function getOutlineMaterialFromCache( originalMaterial ) {
|
193 |
+
|
194 |
+
let data = cache[ originalMaterial.uuid ];
|
195 |
+
|
196 |
+
if ( data === undefined ) {
|
197 |
+
|
198 |
+
data = {
|
199 |
+
material: createMaterial(),
|
200 |
+
used: true,
|
201 |
+
keepAlive: defaultKeepAlive,
|
202 |
+
count: 0
|
203 |
+
};
|
204 |
+
|
205 |
+
cache[ originalMaterial.uuid ] = data;
|
206 |
+
|
207 |
+
}
|
208 |
+
|
209 |
+
data.used = true;
|
210 |
+
|
211 |
+
return data.material;
|
212 |
+
|
213 |
+
}
|
214 |
+
|
215 |
+
function getOutlineMaterial( originalMaterial ) {
|
216 |
+
|
217 |
+
const outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
|
218 |
+
|
219 |
+
originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
|
220 |
+
|
221 |
+
updateOutlineMaterial( outlineMaterial, originalMaterial );
|
222 |
+
|
223 |
+
return outlineMaterial;
|
224 |
+
|
225 |
+
}
|
226 |
+
|
227 |
+
function isCompatible( object ) {
|
228 |
+
|
229 |
+
const geometry = object.geometry;
|
230 |
+
const hasNormals = ( geometry !== undefined ) && ( geometry.attributes.normal !== undefined );
|
231 |
+
|
232 |
+
return ( object.isMesh === true && object.material !== undefined && hasNormals === true );
|
233 |
+
|
234 |
+
}
|
235 |
+
|
236 |
+
function setOutlineMaterial( object ) {
|
237 |
+
|
238 |
+
if ( isCompatible( object ) === false ) return;
|
239 |
+
|
240 |
+
if ( Array.isArray( object.material ) ) {
|
241 |
+
|
242 |
+
for ( let i = 0, il = object.material.length; i < il; i ++ ) {
|
243 |
+
|
244 |
+
object.material[ i ] = getOutlineMaterial( object.material[ i ] );
|
245 |
+
|
246 |
+
}
|
247 |
+
|
248 |
+
} else {
|
249 |
+
|
250 |
+
object.material = getOutlineMaterial( object.material );
|
251 |
+
|
252 |
+
}
|
253 |
+
|
254 |
+
originalOnBeforeRenders[ object.uuid ] = object.onBeforeRender;
|
255 |
+
object.onBeforeRender = onBeforeRender;
|
256 |
+
|
257 |
+
}
|
258 |
+
|
259 |
+
function restoreOriginalMaterial( object ) {
|
260 |
+
|
261 |
+
if ( isCompatible( object ) === false ) return;
|
262 |
+
|
263 |
+
if ( Array.isArray( object.material ) ) {
|
264 |
+
|
265 |
+
for ( let i = 0, il = object.material.length; i < il; i ++ ) {
|
266 |
+
|
267 |
+
object.material[ i ] = originalMaterials[ object.material[ i ].uuid ];
|
268 |
+
|
269 |
+
}
|
270 |
+
|
271 |
+
} else {
|
272 |
+
|
273 |
+
object.material = originalMaterials[ object.material.uuid ];
|
274 |
+
|
275 |
+
}
|
276 |
+
|
277 |
+
object.onBeforeRender = originalOnBeforeRenders[ object.uuid ];
|
278 |
+
|
279 |
+
}
|
280 |
+
|
281 |
+
function onBeforeRender( renderer, scene, camera, geometry, material ) {
|
282 |
+
|
283 |
+
const originalMaterial = originalMaterials[ material.uuid ];
|
284 |
+
|
285 |
+
// just in case
|
286 |
+
if ( originalMaterial === undefined ) return;
|
287 |
+
|
288 |
+
updateUniforms( material, originalMaterial );
|
289 |
+
|
290 |
+
}
|
291 |
+
|
292 |
+
function updateUniforms( material, originalMaterial ) {
|
293 |
+
|
294 |
+
const outlineParameters = originalMaterial.userData.outlineParameters;
|
295 |
+
|
296 |
+
material.uniforms.outlineAlpha.value = originalMaterial.opacity;
|
297 |
+
|
298 |
+
if ( outlineParameters !== undefined ) {
|
299 |
+
|
300 |
+
if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
|
301 |
+
if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.fromArray( outlineParameters.color );
|
302 |
+
if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
|
303 |
+
|
304 |
+
}
|
305 |
+
|
306 |
+
if ( originalMaterial.displacementMap ) {
|
307 |
+
|
308 |
+
material.uniforms.displacementMap.value = originalMaterial.displacementMap;
|
309 |
+
material.uniforms.displacementScale.value = originalMaterial.displacementScale;
|
310 |
+
material.uniforms.displacementBias.value = originalMaterial.displacementBias;
|
311 |
+
|
312 |
+
}
|
313 |
+
|
314 |
+
}
|
315 |
+
|
316 |
+
function updateOutlineMaterial( material, originalMaterial ) {
|
317 |
+
|
318 |
+
if ( material.name === 'invisible' ) return;
|
319 |
+
|
320 |
+
const outlineParameters = originalMaterial.userData.outlineParameters;
|
321 |
+
|
322 |
+
material.fog = originalMaterial.fog;
|
323 |
+
material.toneMapped = originalMaterial.toneMapped;
|
324 |
+
material.premultipliedAlpha = originalMaterial.premultipliedAlpha;
|
325 |
+
material.displacementMap = originalMaterial.displacementMap;
|
326 |
+
|
327 |
+
if ( outlineParameters !== undefined ) {
|
328 |
+
|
329 |
+
if ( originalMaterial.visible === false ) {
|
330 |
+
|
331 |
+
material.visible = false;
|
332 |
+
|
333 |
+
} else {
|
334 |
+
|
335 |
+
material.visible = ( outlineParameters.visible !== undefined ) ? outlineParameters.visible : true;
|
336 |
+
|
337 |
+
}
|
338 |
+
|
339 |
+
material.transparent = ( outlineParameters.alpha !== undefined && outlineParameters.alpha < 1.0 ) ? true : originalMaterial.transparent;
|
340 |
+
|
341 |
+
if ( outlineParameters.keepAlive !== undefined ) cache[ originalMaterial.uuid ].keepAlive = outlineParameters.keepAlive;
|
342 |
+
|
343 |
+
} else {
|
344 |
+
|
345 |
+
material.transparent = originalMaterial.transparent;
|
346 |
+
material.visible = originalMaterial.visible;
|
347 |
+
|
348 |
+
}
|
349 |
+
|
350 |
+
if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
|
351 |
+
|
352 |
+
if ( originalMaterial.clippingPlanes ) {
|
353 |
+
|
354 |
+
material.clipping = true;
|
355 |
+
|
356 |
+
material.clippingPlanes = originalMaterial.clippingPlanes;
|
357 |
+
material.clipIntersection = originalMaterial.clipIntersection;
|
358 |
+
material.clipShadows = originalMaterial.clipShadows;
|
359 |
+
|
360 |
+
}
|
361 |
+
|
362 |
+
material.version = originalMaterial.version; // update outline material if necessary
|
363 |
+
|
364 |
+
}
|
365 |
+
|
366 |
+
function cleanupCache() {
|
367 |
+
|
368 |
+
let keys;
|
369 |
+
|
370 |
+
// clear originalMaterials
|
371 |
+
keys = Object.keys( originalMaterials );
|
372 |
+
|
373 |
+
for ( let i = 0, il = keys.length; i < il; i ++ ) {
|
374 |
+
|
375 |
+
originalMaterials[ keys[ i ] ] = undefined;
|
376 |
+
|
377 |
+
}
|
378 |
+
|
379 |
+
// clear originalOnBeforeRenders
|
380 |
+
keys = Object.keys( originalOnBeforeRenders );
|
381 |
+
|
382 |
+
for ( let i = 0, il = keys.length; i < il; i ++ ) {
|
383 |
+
|
384 |
+
originalOnBeforeRenders[ keys[ i ] ] = undefined;
|
385 |
+
|
386 |
+
}
|
387 |
+
|
388 |
+
// remove unused outlineMaterial from cache
|
389 |
+
keys = Object.keys( cache );
|
390 |
+
|
391 |
+
for ( let i = 0, il = keys.length; i < il; i ++ ) {
|
392 |
+
|
393 |
+
const key = keys[ i ];
|
394 |
+
|
395 |
+
if ( cache[ key ].used === false ) {
|
396 |
+
|
397 |
+
cache[ key ].count ++;
|
398 |
+
|
399 |
+
if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
|
400 |
+
|
401 |
+
delete cache[ key ];
|
402 |
+
|
403 |
+
}
|
404 |
+
|
405 |
+
} else {
|
406 |
+
|
407 |
+
cache[ key ].used = false;
|
408 |
+
cache[ key ].count = 0;
|
409 |
+
|
410 |
+
}
|
411 |
+
|
412 |
+
}
|
413 |
+
|
414 |
+
}
|
415 |
+
|
416 |
+
this.render = function ( scene, camera ) {
|
417 |
+
|
418 |
+
if ( this.enabled === false ) {
|
419 |
+
|
420 |
+
renderer.render( scene, camera );
|
421 |
+
return;
|
422 |
+
|
423 |
+
}
|
424 |
+
|
425 |
+
const currentAutoClear = renderer.autoClear;
|
426 |
+
renderer.autoClear = this.autoClear;
|
427 |
+
|
428 |
+
renderer.render( scene, camera );
|
429 |
+
|
430 |
+
renderer.autoClear = currentAutoClear;
|
431 |
+
|
432 |
+
this.renderOutline( scene, camera );
|
433 |
+
|
434 |
+
};
|
435 |
+
|
436 |
+
this.renderOutline = function ( scene, camera ) {
|
437 |
+
|
438 |
+
const currentAutoClear = renderer.autoClear;
|
439 |
+
const currentSceneAutoUpdate = scene.matrixWorldAutoUpdate;
|
440 |
+
const currentSceneBackground = scene.background;
|
441 |
+
const currentShadowMapEnabled = renderer.shadowMap.enabled;
|
442 |
+
|
443 |
+
scene.matrixWorldAutoUpdate = false;
|
444 |
+
scene.background = null;
|
445 |
+
renderer.autoClear = false;
|
446 |
+
renderer.shadowMap.enabled = false;
|
447 |
+
|
448 |
+
scene.traverse( setOutlineMaterial );
|
449 |
+
|
450 |
+
renderer.render( scene, camera );
|
451 |
+
|
452 |
+
scene.traverse( restoreOriginalMaterial );
|
453 |
+
|
454 |
+
cleanupCache();
|
455 |
+
|
456 |
+
scene.matrixWorldAutoUpdate = currentSceneAutoUpdate;
|
457 |
+
scene.background = currentSceneBackground;
|
458 |
+
renderer.autoClear = currentAutoClear;
|
459 |
+
renderer.shadowMap.enabled = currentShadowMapEnabled;
|
460 |
+
|
461 |
+
};
|
462 |
+
|
463 |
+
/*
|
464 |
+
* See #9918
|
465 |
+
*
|
466 |
+
* The following property copies and wrapper methods enable
|
467 |
+
* OutlineEffect to be called from other *Effect, like
|
468 |
+
*
|
469 |
+
* effect = new StereoEffect( new OutlineEffect( renderer ) );
|
470 |
+
*
|
471 |
+
* function render () {
|
472 |
+
*
|
473 |
+
* effect.render( scene, camera );
|
474 |
+
*
|
475 |
+
* }
|
476 |
+
*/
|
477 |
+
this.autoClear = renderer.autoClear;
|
478 |
+
this.domElement = renderer.domElement;
|
479 |
+
this.shadowMap = renderer.shadowMap;
|
480 |
+
|
481 |
+
this.clear = function ( color, depth, stencil ) {
|
482 |
+
|
483 |
+
renderer.clear( color, depth, stencil );
|
484 |
+
|
485 |
+
};
|
486 |
+
|
487 |
+
this.getPixelRatio = function () {
|
488 |
+
|
489 |
+
return renderer.getPixelRatio();
|
490 |
+
|
491 |
+
};
|
492 |
+
|
493 |
+
this.setPixelRatio = function ( value ) {
|
494 |
+
|
495 |
+
renderer.setPixelRatio( value );
|
496 |
+
|
497 |
+
};
|
498 |
+
|
499 |
+
this.getSize = function ( target ) {
|
500 |
+
|
501 |
+
return renderer.getSize( target );
|
502 |
+
|
503 |
+
};
|
504 |
+
|
505 |
+
this.setSize = function ( width, height, updateStyle ) {
|
506 |
+
|
507 |
+
renderer.setSize( width, height, updateStyle );
|
508 |
+
|
509 |
+
};
|
510 |
+
|
511 |
+
this.setViewport = function ( x, y, width, height ) {
|
512 |
+
|
513 |
+
renderer.setViewport( x, y, width, height );
|
514 |
+
|
515 |
+
};
|
516 |
+
|
517 |
+
this.setScissor = function ( x, y, width, height ) {
|
518 |
+
|
519 |
+
renderer.setScissor( x, y, width, height );
|
520 |
+
|
521 |
+
};
|
522 |
+
|
523 |
+
this.setScissorTest = function ( boolean ) {
|
524 |
+
|
525 |
+
renderer.setScissorTest( boolean );
|
526 |
+
|
527 |
+
};
|
528 |
+
|
529 |
+
this.setRenderTarget = function ( renderTarget ) {
|
530 |
+
|
531 |
+
renderer.setRenderTarget( renderTarget );
|
532 |
+
|
533 |
+
};
|
534 |
+
|
535 |
+
}
|
536 |
+
|
537 |
+
}
|
538 |
+
|
539 |
+
export { OutlineEffect };
|
libs/three.js/0.172.0/jsm/effects/ParallaxBarrierEffect.js
ADDED
@@ -0,0 +1,125 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
LinearFilter,
|
3 |
+
NearestFilter,
|
4 |
+
RGBAFormat,
|
5 |
+
ShaderMaterial,
|
6 |
+
StereoCamera,
|
7 |
+
WebGLRenderTarget
|
8 |
+
} from 'three';
|
9 |
+
import { FullScreenQuad } from '../postprocessing/Pass.js';
|
10 |
+
|
11 |
+
class ParallaxBarrierEffect {
|
12 |
+
|
13 |
+
constructor( renderer ) {
|
14 |
+
|
15 |
+
const _stereo = new StereoCamera();
|
16 |
+
|
17 |
+
const _params = { minFilter: LinearFilter, magFilter: NearestFilter, format: RGBAFormat };
|
18 |
+
|
19 |
+
const _renderTargetL = new WebGLRenderTarget( 512, 512, _params );
|
20 |
+
const _renderTargetR = new WebGLRenderTarget( 512, 512, _params );
|
21 |
+
|
22 |
+
const _material = new ShaderMaterial( {
|
23 |
+
|
24 |
+
uniforms: {
|
25 |
+
|
26 |
+
'mapLeft': { value: _renderTargetL.texture },
|
27 |
+
'mapRight': { value: _renderTargetR.texture }
|
28 |
+
|
29 |
+
},
|
30 |
+
|
31 |
+
vertexShader: [
|
32 |
+
|
33 |
+
'varying vec2 vUv;',
|
34 |
+
|
35 |
+
'void main() {',
|
36 |
+
|
37 |
+
' vUv = vec2( uv.x, uv.y );',
|
38 |
+
' gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
|
39 |
+
|
40 |
+
'}'
|
41 |
+
|
42 |
+
].join( '\n' ),
|
43 |
+
|
44 |
+
fragmentShader: [
|
45 |
+
|
46 |
+
'uniform sampler2D mapLeft;',
|
47 |
+
'uniform sampler2D mapRight;',
|
48 |
+
'varying vec2 vUv;',
|
49 |
+
|
50 |
+
'void main() {',
|
51 |
+
|
52 |
+
' vec2 uv = vUv;',
|
53 |
+
|
54 |
+
' if ( ( mod( gl_FragCoord.y, 2.0 ) ) > 1.00 ) {',
|
55 |
+
|
56 |
+
' gl_FragColor = texture2D( mapLeft, uv );',
|
57 |
+
|
58 |
+
' } else {',
|
59 |
+
|
60 |
+
' gl_FragColor = texture2D( mapRight, uv );',
|
61 |
+
|
62 |
+
' }',
|
63 |
+
|
64 |
+
' #include <tonemapping_fragment>',
|
65 |
+
' #include <colorspace_fragment>',
|
66 |
+
|
67 |
+
'}'
|
68 |
+
|
69 |
+
].join( '\n' )
|
70 |
+
|
71 |
+
} );
|
72 |
+
|
73 |
+
const _quad = new FullScreenQuad( _material );
|
74 |
+
|
75 |
+
this.setSize = function ( width, height ) {
|
76 |
+
|
77 |
+
renderer.setSize( width, height );
|
78 |
+
|
79 |
+
const pixelRatio = renderer.getPixelRatio();
|
80 |
+
|
81 |
+
_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
|
82 |
+
_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
|
83 |
+
|
84 |
+
};
|
85 |
+
|
86 |
+
this.render = function ( scene, camera ) {
|
87 |
+
|
88 |
+
const currentRenderTarget = renderer.getRenderTarget();
|
89 |
+
|
90 |
+
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
|
91 |
+
|
92 |
+
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
|
93 |
+
|
94 |
+
_stereo.update( camera );
|
95 |
+
|
96 |
+
renderer.setRenderTarget( _renderTargetL );
|
97 |
+
renderer.clear();
|
98 |
+
renderer.render( scene, _stereo.cameraL );
|
99 |
+
|
100 |
+
renderer.setRenderTarget( _renderTargetR );
|
101 |
+
renderer.clear();
|
102 |
+
renderer.render( scene, _stereo.cameraR );
|
103 |
+
|
104 |
+
renderer.setRenderTarget( null );
|
105 |
+
_quad.render( renderer );
|
106 |
+
|
107 |
+
renderer.setRenderTarget( currentRenderTarget );
|
108 |
+
|
109 |
+
};
|
110 |
+
|
111 |
+
this.dispose = function () {
|
112 |
+
|
113 |
+
_renderTargetL.dispose();
|
114 |
+
_renderTargetR.dispose();
|
115 |
+
|
116 |
+
_material.dispose();
|
117 |
+
_quad.dispose();
|
118 |
+
|
119 |
+
};
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
}
|
124 |
+
|
125 |
+
export { ParallaxBarrierEffect };
|
libs/three.js/0.172.0/jsm/effects/PeppersGhostEffect.js
ADDED
@@ -0,0 +1,153 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
PerspectiveCamera,
|
3 |
+
Quaternion,
|
4 |
+
Vector3
|
5 |
+
} from 'three';
|
6 |
+
|
7 |
+
/**
|
8 |
+
* peppers ghost effect based on http://www.instructables.com/id/Reflective-Prism/?ALLSTEPS
|
9 |
+
*/
|
10 |
+
|
11 |
+
class PeppersGhostEffect {
|
12 |
+
|
13 |
+
constructor( renderer ) {
|
14 |
+
|
15 |
+
const scope = this;
|
16 |
+
|
17 |
+
scope.cameraDistance = 15;
|
18 |
+
scope.reflectFromAbove = false;
|
19 |
+
|
20 |
+
// Internals
|
21 |
+
let _halfWidth, _width, _height;
|
22 |
+
|
23 |
+
const _cameraF = new PerspectiveCamera(); //front
|
24 |
+
const _cameraB = new PerspectiveCamera(); //back
|
25 |
+
const _cameraL = new PerspectiveCamera(); //left
|
26 |
+
const _cameraR = new PerspectiveCamera(); //right
|
27 |
+
|
28 |
+
const _position = new Vector3();
|
29 |
+
const _quaternion = new Quaternion();
|
30 |
+
const _scale = new Vector3();
|
31 |
+
|
32 |
+
// Initialization
|
33 |
+
renderer.autoClear = false;
|
34 |
+
|
35 |
+
this.setSize = function ( width, height ) {
|
36 |
+
|
37 |
+
_halfWidth = width / 2;
|
38 |
+
if ( width < height ) {
|
39 |
+
|
40 |
+
_width = width / 3;
|
41 |
+
_height = width / 3;
|
42 |
+
|
43 |
+
} else {
|
44 |
+
|
45 |
+
_width = height / 3;
|
46 |
+
_height = height / 3;
|
47 |
+
|
48 |
+
}
|
49 |
+
|
50 |
+
renderer.setSize( width, height );
|
51 |
+
|
52 |
+
};
|
53 |
+
|
54 |
+
this.render = function ( scene, camera ) {
|
55 |
+
|
56 |
+
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
|
57 |
+
|
58 |
+
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
|
59 |
+
|
60 |
+
camera.matrixWorld.decompose( _position, _quaternion, _scale );
|
61 |
+
|
62 |
+
// front
|
63 |
+
_cameraF.position.copy( _position );
|
64 |
+
_cameraF.quaternion.copy( _quaternion );
|
65 |
+
_cameraF.translateZ( scope.cameraDistance );
|
66 |
+
_cameraF.lookAt( scene.position );
|
67 |
+
|
68 |
+
// back
|
69 |
+
_cameraB.position.copy( _position );
|
70 |
+
_cameraB.quaternion.copy( _quaternion );
|
71 |
+
_cameraB.translateZ( - ( scope.cameraDistance ) );
|
72 |
+
_cameraB.lookAt( scene.position );
|
73 |
+
_cameraB.rotation.z += 180 * ( Math.PI / 180 );
|
74 |
+
|
75 |
+
// left
|
76 |
+
_cameraL.position.copy( _position );
|
77 |
+
_cameraL.quaternion.copy( _quaternion );
|
78 |
+
_cameraL.translateX( - ( scope.cameraDistance ) );
|
79 |
+
_cameraL.lookAt( scene.position );
|
80 |
+
_cameraL.rotation.x += 90 * ( Math.PI / 180 );
|
81 |
+
|
82 |
+
// right
|
83 |
+
_cameraR.position.copy( _position );
|
84 |
+
_cameraR.quaternion.copy( _quaternion );
|
85 |
+
_cameraR.translateX( scope.cameraDistance );
|
86 |
+
_cameraR.lookAt( scene.position );
|
87 |
+
_cameraR.rotation.x += 90 * ( Math.PI / 180 );
|
88 |
+
|
89 |
+
|
90 |
+
renderer.clear();
|
91 |
+
renderer.setScissorTest( true );
|
92 |
+
|
93 |
+
renderer.setScissor( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
|
94 |
+
renderer.setViewport( _halfWidth - ( _width / 2 ), ( _height * 2 ), _width, _height );
|
95 |
+
|
96 |
+
if ( scope.reflectFromAbove ) {
|
97 |
+
|
98 |
+
renderer.render( scene, _cameraB );
|
99 |
+
|
100 |
+
} else {
|
101 |
+
|
102 |
+
renderer.render( scene, _cameraF );
|
103 |
+
|
104 |
+
}
|
105 |
+
|
106 |
+
renderer.setScissor( _halfWidth - ( _width / 2 ), 0, _width, _height );
|
107 |
+
renderer.setViewport( _halfWidth - ( _width / 2 ), 0, _width, _height );
|
108 |
+
|
109 |
+
if ( scope.reflectFromAbove ) {
|
110 |
+
|
111 |
+
renderer.render( scene, _cameraF );
|
112 |
+
|
113 |
+
} else {
|
114 |
+
|
115 |
+
renderer.render( scene, _cameraB );
|
116 |
+
|
117 |
+
}
|
118 |
+
|
119 |
+
renderer.setScissor( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
|
120 |
+
renderer.setViewport( _halfWidth - ( _width / 2 ) - _width, _height, _width, _height );
|
121 |
+
|
122 |
+
if ( scope.reflectFromAbove ) {
|
123 |
+
|
124 |
+
renderer.render( scene, _cameraR );
|
125 |
+
|
126 |
+
} else {
|
127 |
+
|
128 |
+
renderer.render( scene, _cameraL );
|
129 |
+
|
130 |
+
}
|
131 |
+
|
132 |
+
renderer.setScissor( _halfWidth + ( _width / 2 ), _height, _width, _height );
|
133 |
+
renderer.setViewport( _halfWidth + ( _width / 2 ), _height, _width, _height );
|
134 |
+
|
135 |
+
if ( scope.reflectFromAbove ) {
|
136 |
+
|
137 |
+
renderer.render( scene, _cameraL );
|
138 |
+
|
139 |
+
} else {
|
140 |
+
|
141 |
+
renderer.render( scene, _cameraR );
|
142 |
+
|
143 |
+
}
|
144 |
+
|
145 |
+
renderer.setScissorTest( false );
|
146 |
+
|
147 |
+
};
|
148 |
+
|
149 |
+
}
|
150 |
+
|
151 |
+
}
|
152 |
+
|
153 |
+
export { PeppersGhostEffect };
|
libs/three.js/0.172.0/jsm/effects/StereoEffect.js
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
StereoCamera,
|
3 |
+
Vector2
|
4 |
+
} from 'three';
|
5 |
+
|
6 |
+
class StereoEffect {
|
7 |
+
|
8 |
+
constructor( renderer ) {
|
9 |
+
|
10 |
+
const _stereo = new StereoCamera();
|
11 |
+
_stereo.aspect = 0.5;
|
12 |
+
const size = new Vector2();
|
13 |
+
|
14 |
+
this.setEyeSeparation = function ( eyeSep ) {
|
15 |
+
|
16 |
+
_stereo.eyeSep = eyeSep;
|
17 |
+
|
18 |
+
};
|
19 |
+
|
20 |
+
this.setSize = function ( width, height ) {
|
21 |
+
|
22 |
+
renderer.setSize( width, height );
|
23 |
+
|
24 |
+
};
|
25 |
+
|
26 |
+
this.render = function ( scene, camera ) {
|
27 |
+
|
28 |
+
if ( scene.matrixWorldAutoUpdate === true ) scene.updateMatrixWorld();
|
29 |
+
|
30 |
+
if ( camera.parent === null && camera.matrixWorldAutoUpdate === true ) camera.updateMatrixWorld();
|
31 |
+
|
32 |
+
_stereo.update( camera );
|
33 |
+
|
34 |
+
const currentAutoClear = renderer.autoClear;
|
35 |
+
renderer.getSize( size );
|
36 |
+
|
37 |
+
renderer.autoClear = false;
|
38 |
+
renderer.clear();
|
39 |
+
|
40 |
+
renderer.setScissorTest( true );
|
41 |
+
|
42 |
+
renderer.setScissor( 0, 0, size.width / 2, size.height );
|
43 |
+
renderer.setViewport( 0, 0, size.width / 2, size.height );
|
44 |
+
renderer.render( scene, _stereo.cameraL );
|
45 |
+
|
46 |
+
renderer.setScissor( size.width / 2, 0, size.width / 2, size.height );
|
47 |
+
renderer.setViewport( size.width / 2, 0, size.width / 2, size.height );
|
48 |
+
renderer.render( scene, _stereo.cameraR );
|
49 |
+
|
50 |
+
renderer.setScissorTest( false );
|
51 |
+
|
52 |
+
renderer.autoClear = currentAutoClear;
|
53 |
+
|
54 |
+
};
|
55 |
+
|
56 |
+
}
|
57 |
+
|
58 |
+
}
|
59 |
+
|
60 |
+
export { StereoEffect };
|
libs/three.js/0.172.0/jsm/environments/DebugEnvironment.js
ADDED
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BackSide,
|
3 |
+
BoxGeometry,
|
4 |
+
Mesh,
|
5 |
+
MeshLambertMaterial,
|
6 |
+
MeshStandardMaterial,
|
7 |
+
PointLight,
|
8 |
+
Scene,
|
9 |
+
} from 'three';
|
10 |
+
|
11 |
+
class DebugEnvironment extends Scene {
|
12 |
+
|
13 |
+
constructor() {
|
14 |
+
|
15 |
+
super();
|
16 |
+
|
17 |
+
const geometry = new BoxGeometry();
|
18 |
+
geometry.deleteAttribute( 'uv' );
|
19 |
+
const roomMaterial = new MeshStandardMaterial( { metalness: 0, side: BackSide } );
|
20 |
+
const room = new Mesh( geometry, roomMaterial );
|
21 |
+
room.scale.setScalar( 10 );
|
22 |
+
this.add( room );
|
23 |
+
|
24 |
+
const mainLight = new PointLight( 0xffffff, 50, 0, 2 );
|
25 |
+
this.add( mainLight );
|
26 |
+
|
27 |
+
const material1 = new MeshLambertMaterial( { color: 0xff0000, emissive: 0xffffff, emissiveIntensity: 10 } );
|
28 |
+
|
29 |
+
const light1 = new Mesh( geometry, material1 );
|
30 |
+
light1.position.set( - 5, 2, 0 );
|
31 |
+
light1.scale.set( 0.1, 1, 1 );
|
32 |
+
this.add( light1 );
|
33 |
+
|
34 |
+
const material2 = new MeshLambertMaterial( { color: 0x00ff00, emissive: 0xffffff, emissiveIntensity: 10 } );
|
35 |
+
|
36 |
+
const light2 = new Mesh( geometry, material2 );
|
37 |
+
light2.position.set( 0, 5, 0 );
|
38 |
+
light2.scale.set( 1, 0.1, 1 );
|
39 |
+
this.add( light2 );
|
40 |
+
|
41 |
+
const material3 = new MeshLambertMaterial( { color: 0x0000ff, emissive: 0xffffff, emissiveIntensity: 10 } );
|
42 |
+
|
43 |
+
const light3 = new Mesh( geometry, material3 );
|
44 |
+
light3.position.set( 2, 1, 5 );
|
45 |
+
light3.scale.set( 1.5, 2, 0.1 );
|
46 |
+
this.add( light3 );
|
47 |
+
|
48 |
+
}
|
49 |
+
|
50 |
+
}
|
51 |
+
|
52 |
+
export { DebugEnvironment };
|
libs/three.js/0.172.0/jsm/environments/RoomEnvironment.js
ADDED
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts
|
3 |
+
*/
|
4 |
+
|
5 |
+
import {
|
6 |
+
BackSide,
|
7 |
+
BoxGeometry,
|
8 |
+
Mesh,
|
9 |
+
MeshBasicMaterial,
|
10 |
+
MeshStandardMaterial,
|
11 |
+
PointLight,
|
12 |
+
Scene,
|
13 |
+
} from 'three';
|
14 |
+
|
15 |
+
class RoomEnvironment extends Scene {
|
16 |
+
|
17 |
+
constructor() {
|
18 |
+
|
19 |
+
super();
|
20 |
+
|
21 |
+
const geometry = new BoxGeometry();
|
22 |
+
geometry.deleteAttribute( 'uv' );
|
23 |
+
|
24 |
+
const roomMaterial = new MeshStandardMaterial( { side: BackSide } );
|
25 |
+
const boxMaterial = new MeshStandardMaterial();
|
26 |
+
|
27 |
+
const mainLight = new PointLight( 0xffffff, 900, 28, 2 );
|
28 |
+
mainLight.position.set( 0.418, 16.199, 0.300 );
|
29 |
+
this.add( mainLight );
|
30 |
+
|
31 |
+
const room = new Mesh( geometry, roomMaterial );
|
32 |
+
room.position.set( - 0.757, 13.219, 0.717 );
|
33 |
+
room.scale.set( 31.713, 28.305, 28.591 );
|
34 |
+
this.add( room );
|
35 |
+
|
36 |
+
const box1 = new Mesh( geometry, boxMaterial );
|
37 |
+
box1.position.set( - 10.906, 2.009, 1.846 );
|
38 |
+
box1.rotation.set( 0, - 0.195, 0 );
|
39 |
+
box1.scale.set( 2.328, 7.905, 4.651 );
|
40 |
+
this.add( box1 );
|
41 |
+
|
42 |
+
const box2 = new Mesh( geometry, boxMaterial );
|
43 |
+
box2.position.set( - 5.607, - 0.754, - 0.758 );
|
44 |
+
box2.rotation.set( 0, 0.994, 0 );
|
45 |
+
box2.scale.set( 1.970, 1.534, 3.955 );
|
46 |
+
this.add( box2 );
|
47 |
+
|
48 |
+
const box3 = new Mesh( geometry, boxMaterial );
|
49 |
+
box3.position.set( 6.167, 0.857, 7.803 );
|
50 |
+
box3.rotation.set( 0, 0.561, 0 );
|
51 |
+
box3.scale.set( 3.927, 6.285, 3.687 );
|
52 |
+
this.add( box3 );
|
53 |
+
|
54 |
+
const box4 = new Mesh( geometry, boxMaterial );
|
55 |
+
box4.position.set( - 2.017, 0.018, 6.124 );
|
56 |
+
box4.rotation.set( 0, 0.333, 0 );
|
57 |
+
box4.scale.set( 2.002, 4.566, 2.064 );
|
58 |
+
this.add( box4 );
|
59 |
+
|
60 |
+
const box5 = new Mesh( geometry, boxMaterial );
|
61 |
+
box5.position.set( 2.291, - 0.756, - 2.621 );
|
62 |
+
box5.rotation.set( 0, - 0.286, 0 );
|
63 |
+
box5.scale.set( 1.546, 1.552, 1.496 );
|
64 |
+
this.add( box5 );
|
65 |
+
|
66 |
+
const box6 = new Mesh( geometry, boxMaterial );
|
67 |
+
box6.position.set( - 2.193, - 0.369, - 5.547 );
|
68 |
+
box6.rotation.set( 0, 0.516, 0 );
|
69 |
+
box6.scale.set( 3.875, 3.487, 2.986 );
|
70 |
+
this.add( box6 );
|
71 |
+
|
72 |
+
|
73 |
+
// -x right
|
74 |
+
const light1 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
|
75 |
+
light1.position.set( - 16.116, 14.37, 8.208 );
|
76 |
+
light1.scale.set( 0.1, 2.428, 2.739 );
|
77 |
+
this.add( light1 );
|
78 |
+
|
79 |
+
// -x left
|
80 |
+
const light2 = new Mesh( geometry, createAreaLightMaterial( 50 ) );
|
81 |
+
light2.position.set( - 16.109, 18.021, - 8.207 );
|
82 |
+
light2.scale.set( 0.1, 2.425, 2.751 );
|
83 |
+
this.add( light2 );
|
84 |
+
|
85 |
+
// +x
|
86 |
+
const light3 = new Mesh( geometry, createAreaLightMaterial( 17 ) );
|
87 |
+
light3.position.set( 14.904, 12.198, - 1.832 );
|
88 |
+
light3.scale.set( 0.15, 4.265, 6.331 );
|
89 |
+
this.add( light3 );
|
90 |
+
|
91 |
+
// +z
|
92 |
+
const light4 = new Mesh( geometry, createAreaLightMaterial( 43 ) );
|
93 |
+
light4.position.set( - 0.462, 8.89, 14.520 );
|
94 |
+
light4.scale.set( 4.38, 5.441, 0.088 );
|
95 |
+
this.add( light4 );
|
96 |
+
|
97 |
+
// -z
|
98 |
+
const light5 = new Mesh( geometry, createAreaLightMaterial( 20 ) );
|
99 |
+
light5.position.set( 3.235, 11.486, - 12.541 );
|
100 |
+
light5.scale.set( 2.5, 2.0, 0.1 );
|
101 |
+
this.add( light5 );
|
102 |
+
|
103 |
+
// +y
|
104 |
+
const light6 = new Mesh( geometry, createAreaLightMaterial( 100 ) );
|
105 |
+
light6.position.set( 0.0, 20.0, 0.0 );
|
106 |
+
light6.scale.set( 1.0, 0.1, 1.0 );
|
107 |
+
this.add( light6 );
|
108 |
+
|
109 |
+
}
|
110 |
+
|
111 |
+
dispose() {
|
112 |
+
|
113 |
+
const resources = new Set();
|
114 |
+
|
115 |
+
this.traverse( ( object ) => {
|
116 |
+
|
117 |
+
if ( object.isMesh ) {
|
118 |
+
|
119 |
+
resources.add( object.geometry );
|
120 |
+
resources.add( object.material );
|
121 |
+
|
122 |
+
}
|
123 |
+
|
124 |
+
} );
|
125 |
+
|
126 |
+
for ( const resource of resources ) {
|
127 |
+
|
128 |
+
resource.dispose();
|
129 |
+
|
130 |
+
}
|
131 |
+
|
132 |
+
}
|
133 |
+
|
134 |
+
}
|
135 |
+
|
136 |
+
function createAreaLightMaterial( intensity ) {
|
137 |
+
|
138 |
+
const material = new MeshBasicMaterial();
|
139 |
+
material.color.setScalar( intensity );
|
140 |
+
return material;
|
141 |
+
|
142 |
+
}
|
143 |
+
|
144 |
+
export { RoomEnvironment };
|
libs/three.js/0.172.0/jsm/exporters/DRACOExporter.js
ADDED
@@ -0,0 +1,269 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Color, ColorManagement, SRGBColorSpace } from 'three';
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Export draco compressed files from threejs geometry objects.
|
5 |
+
*
|
6 |
+
* Draco files are compressed and usually are smaller than conventional 3D file formats.
|
7 |
+
*
|
8 |
+
* The exporter receives a options object containing
|
9 |
+
* - decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality)
|
10 |
+
* - encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality)
|
11 |
+
* - encoderMethod
|
12 |
+
* - quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC)
|
13 |
+
* - exportUvs
|
14 |
+
* - exportNormals
|
15 |
+
* - exportColor
|
16 |
+
*/
|
17 |
+
|
18 |
+
/* global DracoEncoderModule */
|
19 |
+
|
20 |
+
class DRACOExporter {
|
21 |
+
|
22 |
+
parse( object, options = {} ) {
|
23 |
+
|
24 |
+
options = Object.assign( {
|
25 |
+
decodeSpeed: 5,
|
26 |
+
encodeSpeed: 5,
|
27 |
+
encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
|
28 |
+
quantization: [ 16, 8, 8, 8, 8 ],
|
29 |
+
exportUvs: true,
|
30 |
+
exportNormals: true,
|
31 |
+
exportColor: false,
|
32 |
+
}, options );
|
33 |
+
|
34 |
+
if ( DracoEncoderModule === undefined ) {
|
35 |
+
|
36 |
+
throw new Error( 'THREE.DRACOExporter: required the draco_encoder to work.' );
|
37 |
+
|
38 |
+
}
|
39 |
+
|
40 |
+
const geometry = object.geometry;
|
41 |
+
|
42 |
+
const dracoEncoder = DracoEncoderModule();
|
43 |
+
const encoder = new dracoEncoder.Encoder();
|
44 |
+
let builder;
|
45 |
+
let dracoObject;
|
46 |
+
|
47 |
+
if ( object.isMesh === true ) {
|
48 |
+
|
49 |
+
builder = new dracoEncoder.MeshBuilder();
|
50 |
+
dracoObject = new dracoEncoder.Mesh();
|
51 |
+
|
52 |
+
const vertices = geometry.getAttribute( 'position' );
|
53 |
+
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
|
54 |
+
|
55 |
+
const faces = geometry.getIndex();
|
56 |
+
|
57 |
+
if ( faces !== null ) {
|
58 |
+
|
59 |
+
builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array );
|
60 |
+
|
61 |
+
} else {
|
62 |
+
|
63 |
+
const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count );
|
64 |
+
|
65 |
+
for ( let i = 0; i < faces.length; i ++ ) {
|
66 |
+
|
67 |
+
faces[ i ] = i;
|
68 |
+
|
69 |
+
}
|
70 |
+
|
71 |
+
builder.AddFacesToMesh( dracoObject, vertices.count, faces );
|
72 |
+
|
73 |
+
}
|
74 |
+
|
75 |
+
if ( options.exportNormals === true ) {
|
76 |
+
|
77 |
+
const normals = geometry.getAttribute( 'normal' );
|
78 |
+
|
79 |
+
if ( normals !== undefined ) {
|
80 |
+
|
81 |
+
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );
|
82 |
+
|
83 |
+
}
|
84 |
+
|
85 |
+
}
|
86 |
+
|
87 |
+
if ( options.exportUvs === true ) {
|
88 |
+
|
89 |
+
const uvs = geometry.getAttribute( 'uv' );
|
90 |
+
|
91 |
+
if ( uvs !== undefined ) {
|
92 |
+
|
93 |
+
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array );
|
94 |
+
|
95 |
+
}
|
96 |
+
|
97 |
+
}
|
98 |
+
|
99 |
+
if ( options.exportColor === true ) {
|
100 |
+
|
101 |
+
const colors = geometry.getAttribute( 'color' );
|
102 |
+
|
103 |
+
if ( colors !== undefined ) {
|
104 |
+
|
105 |
+
const array = createVertexColorSRGBArray( colors );
|
106 |
+
|
107 |
+
builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array );
|
108 |
+
|
109 |
+
}
|
110 |
+
|
111 |
+
}
|
112 |
+
|
113 |
+
} else if ( object.isPoints === true ) {
|
114 |
+
|
115 |
+
builder = new dracoEncoder.PointCloudBuilder();
|
116 |
+
dracoObject = new dracoEncoder.PointCloud();
|
117 |
+
|
118 |
+
const vertices = geometry.getAttribute( 'position' );
|
119 |
+
builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
|
120 |
+
|
121 |
+
if ( options.exportColor === true ) {
|
122 |
+
|
123 |
+
const colors = geometry.getAttribute( 'color' );
|
124 |
+
|
125 |
+
if ( colors !== undefined ) {
|
126 |
+
|
127 |
+
const array = createVertexColorSRGBArray( colors );
|
128 |
+
|
129 |
+
builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, array );
|
130 |
+
|
131 |
+
}
|
132 |
+
|
133 |
+
}
|
134 |
+
|
135 |
+
} else {
|
136 |
+
|
137 |
+
throw new Error( 'DRACOExporter: Unsupported object type.' );
|
138 |
+
|
139 |
+
}
|
140 |
+
|
141 |
+
//Compress using draco encoder
|
142 |
+
|
143 |
+
const encodedData = new dracoEncoder.DracoInt8Array();
|
144 |
+
|
145 |
+
//Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).
|
146 |
+
|
147 |
+
const encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5;
|
148 |
+
const decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5;
|
149 |
+
|
150 |
+
encoder.SetSpeedOptions( encodeSpeed, decodeSpeed );
|
151 |
+
|
152 |
+
// Sets the desired encoding method for a given geometry.
|
153 |
+
|
154 |
+
if ( options.encoderMethod !== undefined ) {
|
155 |
+
|
156 |
+
encoder.SetEncodingMethod( options.encoderMethod );
|
157 |
+
|
158 |
+
}
|
159 |
+
|
160 |
+
// Sets the quantization (number of bits used to represent) compression options for a named attribute.
|
161 |
+
// The attribute values will be quantized in a box defined by the maximum extent of the attribute values.
|
162 |
+
if ( options.quantization !== undefined ) {
|
163 |
+
|
164 |
+
for ( let i = 0; i < 5; i ++ ) {
|
165 |
+
|
166 |
+
if ( options.quantization[ i ] !== undefined ) {
|
167 |
+
|
168 |
+
encoder.SetAttributeQuantization( i, options.quantization[ i ] );
|
169 |
+
|
170 |
+
}
|
171 |
+
|
172 |
+
}
|
173 |
+
|
174 |
+
}
|
175 |
+
|
176 |
+
let length;
|
177 |
+
|
178 |
+
if ( object.isMesh === true ) {
|
179 |
+
|
180 |
+
length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );
|
181 |
+
|
182 |
+
} else {
|
183 |
+
|
184 |
+
length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );
|
185 |
+
|
186 |
+
}
|
187 |
+
|
188 |
+
dracoEncoder.destroy( dracoObject );
|
189 |
+
|
190 |
+
if ( length === 0 ) {
|
191 |
+
|
192 |
+
throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );
|
193 |
+
|
194 |
+
}
|
195 |
+
|
196 |
+
//Copy encoded data to buffer.
|
197 |
+
const outputData = new Int8Array( new ArrayBuffer( length ) );
|
198 |
+
|
199 |
+
for ( let i = 0; i < length; i ++ ) {
|
200 |
+
|
201 |
+
outputData[ i ] = encodedData.GetValue( i );
|
202 |
+
|
203 |
+
}
|
204 |
+
|
205 |
+
dracoEncoder.destroy( encodedData );
|
206 |
+
dracoEncoder.destroy( encoder );
|
207 |
+
dracoEncoder.destroy( builder );
|
208 |
+
|
209 |
+
return outputData;
|
210 |
+
|
211 |
+
}
|
212 |
+
|
213 |
+
}
|
214 |
+
|
215 |
+
function createVertexColorSRGBArray( attribute ) {
|
216 |
+
|
217 |
+
// While .drc files do not specify colorspace, the only 'official' tooling
|
218 |
+
// is PLY and OBJ converters, which use sRGB. We'll assume sRGB is expected
|
219 |
+
// for .drc files, but note that Draco buffers embedded in glTF files will
|
220 |
+
// be Linear-sRGB instead.
|
221 |
+
|
222 |
+
const _color = new Color();
|
223 |
+
|
224 |
+
const count = attribute.count;
|
225 |
+
const itemSize = attribute.itemSize;
|
226 |
+
const array = new Float32Array( count * itemSize );
|
227 |
+
|
228 |
+
for ( let i = 0, il = count; i < il; i ++ ) {
|
229 |
+
|
230 |
+
_color.fromBufferAttribute( attribute, i );
|
231 |
+
|
232 |
+
ColorManagement.fromWorkingColorSpace( _color, SRGBColorSpace );
|
233 |
+
|
234 |
+
array[ i * itemSize ] = _color.r;
|
235 |
+
array[ i * itemSize + 1 ] = _color.g;
|
236 |
+
array[ i * itemSize + 2 ] = _color.b;
|
237 |
+
|
238 |
+
if ( itemSize === 4 ) {
|
239 |
+
|
240 |
+
array[ i * itemSize + 3 ] = attribute.getW( i );
|
241 |
+
|
242 |
+
}
|
243 |
+
|
244 |
+
}
|
245 |
+
|
246 |
+
return array;
|
247 |
+
|
248 |
+
}
|
249 |
+
|
250 |
+
// Encoder methods
|
251 |
+
|
252 |
+
DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1;
|
253 |
+
DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0;
|
254 |
+
|
255 |
+
// Geometry type
|
256 |
+
|
257 |
+
DRACOExporter.POINT_CLOUD = 0;
|
258 |
+
DRACOExporter.TRIANGULAR_MESH = 1;
|
259 |
+
|
260 |
+
// Attribute type
|
261 |
+
|
262 |
+
DRACOExporter.INVALID = - 1;
|
263 |
+
DRACOExporter.POSITION = 0;
|
264 |
+
DRACOExporter.NORMAL = 1;
|
265 |
+
DRACOExporter.COLOR = 2;
|
266 |
+
DRACOExporter.TEX_COORD = 3;
|
267 |
+
DRACOExporter.GENERIC = 4;
|
268 |
+
|
269 |
+
export { DRACOExporter };
|
libs/three.js/0.172.0/jsm/exporters/EXRExporter.js
ADDED
@@ -0,0 +1,587 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* @author sciecode / https://github.com/sciecode
|
3 |
+
*
|
4 |
+
* EXR format references:
|
5 |
+
* https://www.openexr.com/documentation/openexrfilelayout.pdf
|
6 |
+
*/
|
7 |
+
|
8 |
+
import {
|
9 |
+
FloatType,
|
10 |
+
HalfFloatType,
|
11 |
+
RGBAFormat,
|
12 |
+
DataUtils,
|
13 |
+
} from 'three';
|
14 |
+
import * as fflate from '../libs/fflate.module.js';
|
15 |
+
|
16 |
+
const textEncoder = new TextEncoder();
|
17 |
+
|
18 |
+
const NO_COMPRESSION = 0;
|
19 |
+
const ZIPS_COMPRESSION = 2;
|
20 |
+
const ZIP_COMPRESSION = 3;
|
21 |
+
|
22 |
+
class EXRExporter {
|
23 |
+
|
24 |
+
async parse( arg1, arg2, arg3 ) {
|
25 |
+
|
26 |
+
if ( ! arg1 || ! ( arg1.isWebGLRenderer || arg1.isWebGPURenderer || arg1.isDataTexture ) ) {
|
27 |
+
|
28 |
+
throw Error( 'EXRExporter.parse: Unsupported first parameter, expected instance of WebGLRenderer or DataTexture.' );
|
29 |
+
|
30 |
+
} else if ( arg1.isWebGLRenderer || arg1.isWebGPURenderer ) {
|
31 |
+
|
32 |
+
const renderer = arg1, renderTarget = arg2, options = arg3;
|
33 |
+
|
34 |
+
supportedRTT( renderTarget );
|
35 |
+
|
36 |
+
const info = buildInfoRTT( renderTarget, options ),
|
37 |
+
dataBuffer = await getPixelData( renderer, renderTarget, info ),
|
38 |
+
rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
|
39 |
+
chunks = compressData( rawContentBuffer, info );
|
40 |
+
|
41 |
+
return fillData( chunks, info );
|
42 |
+
|
43 |
+
} else if ( arg1.isDataTexture ) {
|
44 |
+
|
45 |
+
const texture = arg1, options = arg2;
|
46 |
+
|
47 |
+
supportedDT( texture );
|
48 |
+
|
49 |
+
const info = buildInfoDT( texture, options ),
|
50 |
+
dataBuffer = texture.image.data,
|
51 |
+
rawContentBuffer = reorganizeDataBuffer( dataBuffer, info ),
|
52 |
+
chunks = compressData( rawContentBuffer, info );
|
53 |
+
|
54 |
+
return fillData( chunks, info );
|
55 |
+
|
56 |
+
}
|
57 |
+
|
58 |
+
}
|
59 |
+
|
60 |
+
}
|
61 |
+
|
62 |
+
function supportedRTT( renderTarget ) {
|
63 |
+
|
64 |
+
if ( ! renderTarget || ! renderTarget.isRenderTarget ) {
|
65 |
+
|
66 |
+
throw Error( 'EXRExporter.parse: Unsupported second parameter, expected instance of WebGLRenderTarget.' );
|
67 |
+
|
68 |
+
}
|
69 |
+
|
70 |
+
if ( renderTarget.isWebGLCubeRenderTarget || renderTarget.isWebGL3DRenderTarget || renderTarget.isWebGLArrayRenderTarget ) {
|
71 |
+
|
72 |
+
throw Error( 'EXRExporter.parse: Unsupported render target type, expected instance of WebGLRenderTarget.' );
|
73 |
+
|
74 |
+
}
|
75 |
+
|
76 |
+
if ( renderTarget.texture.type !== FloatType && renderTarget.texture.type !== HalfFloatType ) {
|
77 |
+
|
78 |
+
throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture type.' );
|
79 |
+
|
80 |
+
}
|
81 |
+
|
82 |
+
if ( renderTarget.texture.format !== RGBAFormat ) {
|
83 |
+
|
84 |
+
throw Error( 'EXRExporter.parse: Unsupported WebGLRenderTarget texture format, expected RGBAFormat.' );
|
85 |
+
|
86 |
+
}
|
87 |
+
|
88 |
+
}
|
89 |
+
|
90 |
+
function supportedDT( texture ) {
|
91 |
+
|
92 |
+
if ( texture.type !== FloatType && texture.type !== HalfFloatType ) {
|
93 |
+
|
94 |
+
throw Error( 'EXRExporter.parse: Unsupported DataTexture texture type.' );
|
95 |
+
|
96 |
+
}
|
97 |
+
|
98 |
+
if ( texture.format !== RGBAFormat ) {
|
99 |
+
|
100 |
+
throw Error( 'EXRExporter.parse: Unsupported DataTexture texture format, expected RGBAFormat.' );
|
101 |
+
|
102 |
+
}
|
103 |
+
|
104 |
+
if ( ! texture.image.data ) {
|
105 |
+
|
106 |
+
throw Error( 'EXRExporter.parse: Invalid DataTexture image data.' );
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
if ( texture.type === FloatType && texture.image.data.constructor.name !== 'Float32Array' ) {
|
111 |
+
|
112 |
+
throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Float32Array\'.' );
|
113 |
+
|
114 |
+
}
|
115 |
+
|
116 |
+
if ( texture.type === HalfFloatType && texture.image.data.constructor.name !== 'Uint16Array' ) {
|
117 |
+
|
118 |
+
throw Error( 'EXRExporter.parse: DataTexture image data doesn\'t match type, expected \'Uint16Array\'.' );
|
119 |
+
|
120 |
+
}
|
121 |
+
|
122 |
+
}
|
123 |
+
|
124 |
+
function buildInfoRTT( renderTarget, options = {} ) {
|
125 |
+
|
126 |
+
const compressionSizes = {
|
127 |
+
0: 1,
|
128 |
+
2: 1,
|
129 |
+
3: 16
|
130 |
+
};
|
131 |
+
|
132 |
+
const WIDTH = renderTarget.width,
|
133 |
+
HEIGHT = renderTarget.height,
|
134 |
+
TYPE = renderTarget.texture.type,
|
135 |
+
FORMAT = renderTarget.texture.format,
|
136 |
+
COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
|
137 |
+
EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
|
138 |
+
OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
|
139 |
+
COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
|
140 |
+
NUM_CHANNELS = 4;
|
141 |
+
|
142 |
+
return {
|
143 |
+
width: WIDTH,
|
144 |
+
height: HEIGHT,
|
145 |
+
type: TYPE,
|
146 |
+
format: FORMAT,
|
147 |
+
compression: COMPRESSION,
|
148 |
+
blockLines: COMPRESSION_SIZE,
|
149 |
+
dataType: OUT_TYPE,
|
150 |
+
dataSize: 2 * OUT_TYPE,
|
151 |
+
numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
|
152 |
+
numInputChannels: 4,
|
153 |
+
numOutputChannels: NUM_CHANNELS,
|
154 |
+
};
|
155 |
+
|
156 |
+
}
|
157 |
+
|
158 |
+
function buildInfoDT( texture, options = {} ) {
|
159 |
+
|
160 |
+
const compressionSizes = {
|
161 |
+
0: 1,
|
162 |
+
2: 1,
|
163 |
+
3: 16
|
164 |
+
};
|
165 |
+
|
166 |
+
const WIDTH = texture.image.width,
|
167 |
+
HEIGHT = texture.image.height,
|
168 |
+
TYPE = texture.type,
|
169 |
+
FORMAT = texture.format,
|
170 |
+
COMPRESSION = ( options.compression !== undefined ) ? options.compression : ZIP_COMPRESSION,
|
171 |
+
EXPORTER_TYPE = ( options.type !== undefined ) ? options.type : HalfFloatType,
|
172 |
+
OUT_TYPE = ( EXPORTER_TYPE === FloatType ) ? 2 : 1,
|
173 |
+
COMPRESSION_SIZE = compressionSizes[ COMPRESSION ],
|
174 |
+
NUM_CHANNELS = 4;
|
175 |
+
|
176 |
+
return {
|
177 |
+
width: WIDTH,
|
178 |
+
height: HEIGHT,
|
179 |
+
type: TYPE,
|
180 |
+
format: FORMAT,
|
181 |
+
compression: COMPRESSION,
|
182 |
+
blockLines: COMPRESSION_SIZE,
|
183 |
+
dataType: OUT_TYPE,
|
184 |
+
dataSize: 2 * OUT_TYPE,
|
185 |
+
numBlocks: Math.ceil( HEIGHT / COMPRESSION_SIZE ),
|
186 |
+
numInputChannels: 4,
|
187 |
+
numOutputChannels: NUM_CHANNELS,
|
188 |
+
};
|
189 |
+
|
190 |
+
}
|
191 |
+
|
192 |
+
async function getPixelData( renderer, rtt, info ) {
|
193 |
+
|
194 |
+
let dataBuffer;
|
195 |
+
|
196 |
+
if ( renderer.isWebGLRenderer ) {
|
197 |
+
|
198 |
+
if ( info.type === FloatType ) {
|
199 |
+
|
200 |
+
dataBuffer = new Float32Array( info.width * info.height * info.numInputChannels );
|
201 |
+
|
202 |
+
} else {
|
203 |
+
|
204 |
+
dataBuffer = new Uint16Array( info.width * info.height * info.numInputChannels );
|
205 |
+
|
206 |
+
}
|
207 |
+
|
208 |
+
await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, info.width, info.height, dataBuffer );
|
209 |
+
|
210 |
+
} else {
|
211 |
+
|
212 |
+
dataBuffer = await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, info.width, info.height );
|
213 |
+
|
214 |
+
}
|
215 |
+
|
216 |
+
return dataBuffer;
|
217 |
+
|
218 |
+
}
|
219 |
+
|
220 |
+
function reorganizeDataBuffer( inBuffer, info ) {
|
221 |
+
|
222 |
+
const w = info.width,
|
223 |
+
h = info.height,
|
224 |
+
dec = { r: 0, g: 0, b: 0, a: 0 },
|
225 |
+
offset = { value: 0 },
|
226 |
+
cOffset = ( info.numOutputChannels == 4 ) ? 1 : 0,
|
227 |
+
getValue = ( info.type == FloatType ) ? getFloat32 : getFloat16,
|
228 |
+
setValue = ( info.dataType == 1 ) ? setFloat16 : setFloat32,
|
229 |
+
outBuffer = new Uint8Array( info.width * info.height * info.numOutputChannels * info.dataSize ),
|
230 |
+
dv = new DataView( outBuffer.buffer );
|
231 |
+
|
232 |
+
for ( let y = 0; y < h; ++ y ) {
|
233 |
+
|
234 |
+
for ( let x = 0; x < w; ++ x ) {
|
235 |
+
|
236 |
+
const i = y * w * 4 + x * 4;
|
237 |
+
|
238 |
+
const r = getValue( inBuffer, i );
|
239 |
+
const g = getValue( inBuffer, i + 1 );
|
240 |
+
const b = getValue( inBuffer, i + 2 );
|
241 |
+
const a = getValue( inBuffer, i + 3 );
|
242 |
+
|
243 |
+
const line = ( h - y - 1 ) * w * ( 3 + cOffset ) * info.dataSize;
|
244 |
+
|
245 |
+
decodeLinear( dec, r, g, b, a );
|
246 |
+
|
247 |
+
offset.value = line + x * info.dataSize;
|
248 |
+
setValue( dv, dec.a, offset );
|
249 |
+
|
250 |
+
offset.value = line + ( cOffset ) * w * info.dataSize + x * info.dataSize;
|
251 |
+
setValue( dv, dec.b, offset );
|
252 |
+
|
253 |
+
offset.value = line + ( 1 + cOffset ) * w * info.dataSize + x * info.dataSize;
|
254 |
+
setValue( dv, dec.g, offset );
|
255 |
+
|
256 |
+
offset.value = line + ( 2 + cOffset ) * w * info.dataSize + x * info.dataSize;
|
257 |
+
setValue( dv, dec.r, offset );
|
258 |
+
|
259 |
+
}
|
260 |
+
|
261 |
+
}
|
262 |
+
|
263 |
+
return outBuffer;
|
264 |
+
|
265 |
+
}
|
266 |
+
|
267 |
+
function compressData( inBuffer, info ) {
|
268 |
+
|
269 |
+
let compress,
|
270 |
+
tmpBuffer,
|
271 |
+
sum = 0;
|
272 |
+
|
273 |
+
const chunks = { data: new Array(), totalSize: 0 },
|
274 |
+
size = info.width * info.numOutputChannels * info.blockLines * info.dataSize;
|
275 |
+
|
276 |
+
switch ( info.compression ) {
|
277 |
+
|
278 |
+
case 0:
|
279 |
+
compress = compressNONE;
|
280 |
+
break;
|
281 |
+
|
282 |
+
case 2:
|
283 |
+
case 3:
|
284 |
+
compress = compressZIP;
|
285 |
+
break;
|
286 |
+
|
287 |
+
}
|
288 |
+
|
289 |
+
if ( info.compression !== 0 ) {
|
290 |
+
|
291 |
+
tmpBuffer = new Uint8Array( size );
|
292 |
+
|
293 |
+
}
|
294 |
+
|
295 |
+
for ( let i = 0; i < info.numBlocks; ++ i ) {
|
296 |
+
|
297 |
+
const arr = inBuffer.subarray( size * i, size * ( i + 1 ) );
|
298 |
+
|
299 |
+
const block = compress( arr, tmpBuffer );
|
300 |
+
|
301 |
+
sum += block.length;
|
302 |
+
|
303 |
+
chunks.data.push( { dataChunk: block, size: block.length } );
|
304 |
+
|
305 |
+
}
|
306 |
+
|
307 |
+
chunks.totalSize = sum;
|
308 |
+
|
309 |
+
return chunks;
|
310 |
+
|
311 |
+
}
|
312 |
+
|
313 |
+
function compressNONE( data ) {
|
314 |
+
|
315 |
+
return data;
|
316 |
+
|
317 |
+
}
|
318 |
+
|
319 |
+
function compressZIP( data, tmpBuffer ) {
|
320 |
+
|
321 |
+
//
|
322 |
+
// Reorder the pixel data.
|
323 |
+
//
|
324 |
+
|
325 |
+
let t1 = 0,
|
326 |
+
t2 = Math.floor( ( data.length + 1 ) / 2 ),
|
327 |
+
s = 0;
|
328 |
+
|
329 |
+
const stop = data.length - 1;
|
330 |
+
|
331 |
+
while ( true ) {
|
332 |
+
|
333 |
+
if ( s > stop ) break;
|
334 |
+
tmpBuffer[ t1 ++ ] = data[ s ++ ];
|
335 |
+
|
336 |
+
if ( s > stop ) break;
|
337 |
+
tmpBuffer[ t2 ++ ] = data[ s ++ ];
|
338 |
+
|
339 |
+
}
|
340 |
+
|
341 |
+
//
|
342 |
+
// Predictor.
|
343 |
+
//
|
344 |
+
|
345 |
+
let p = tmpBuffer[ 0 ];
|
346 |
+
|
347 |
+
for ( let t = 1; t < tmpBuffer.length; t ++ ) {
|
348 |
+
|
349 |
+
const d = tmpBuffer[ t ] - p + ( 128 + 256 );
|
350 |
+
p = tmpBuffer[ t ];
|
351 |
+
tmpBuffer[ t ] = d;
|
352 |
+
|
353 |
+
}
|
354 |
+
|
355 |
+
const deflate = fflate.zlibSync( tmpBuffer );
|
356 |
+
|
357 |
+
return deflate;
|
358 |
+
|
359 |
+
}
|
360 |
+
|
361 |
+
function fillHeader( outBuffer, chunks, info ) {
|
362 |
+
|
363 |
+
const offset = { value: 0 };
|
364 |
+
const dv = new DataView( outBuffer.buffer );
|
365 |
+
|
366 |
+
setUint32( dv, 20000630, offset ); // magic
|
367 |
+
setUint32( dv, 2, offset ); // mask
|
368 |
+
|
369 |
+
// = HEADER =
|
370 |
+
|
371 |
+
setString( dv, 'compression', offset );
|
372 |
+
setString( dv, 'compression', offset );
|
373 |
+
setUint32( dv, 1, offset );
|
374 |
+
setUint8( dv, info.compression, offset );
|
375 |
+
|
376 |
+
setString( dv, 'screenWindowCenter', offset );
|
377 |
+
setString( dv, 'v2f', offset );
|
378 |
+
setUint32( dv, 8, offset );
|
379 |
+
setUint32( dv, 0, offset );
|
380 |
+
setUint32( dv, 0, offset );
|
381 |
+
|
382 |
+
setString( dv, 'screenWindowWidth', offset );
|
383 |
+
setString( dv, 'float', offset );
|
384 |
+
setUint32( dv, 4, offset );
|
385 |
+
setFloat32( dv, 1.0, offset );
|
386 |
+
|
387 |
+
setString( dv, 'pixelAspectRatio', offset );
|
388 |
+
setString( dv, 'float', offset );
|
389 |
+
setUint32( dv, 4, offset );
|
390 |
+
setFloat32( dv, 1.0, offset );
|
391 |
+
|
392 |
+
setString( dv, 'lineOrder', offset );
|
393 |
+
setString( dv, 'lineOrder', offset );
|
394 |
+
setUint32( dv, 1, offset );
|
395 |
+
setUint8( dv, 0, offset );
|
396 |
+
|
397 |
+
setString( dv, 'dataWindow', offset );
|
398 |
+
setString( dv, 'box2i', offset );
|
399 |
+
setUint32( dv, 16, offset );
|
400 |
+
setUint32( dv, 0, offset );
|
401 |
+
setUint32( dv, 0, offset );
|
402 |
+
setUint32( dv, info.width - 1, offset );
|
403 |
+
setUint32( dv, info.height - 1, offset );
|
404 |
+
|
405 |
+
setString( dv, 'displayWindow', offset );
|
406 |
+
setString( dv, 'box2i', offset );
|
407 |
+
setUint32( dv, 16, offset );
|
408 |
+
setUint32( dv, 0, offset );
|
409 |
+
setUint32( dv, 0, offset );
|
410 |
+
setUint32( dv, info.width - 1, offset );
|
411 |
+
setUint32( dv, info.height - 1, offset );
|
412 |
+
|
413 |
+
setString( dv, 'channels', offset );
|
414 |
+
setString( dv, 'chlist', offset );
|
415 |
+
setUint32( dv, info.numOutputChannels * 18 + 1, offset );
|
416 |
+
|
417 |
+
setString( dv, 'A', offset );
|
418 |
+
setUint32( dv, info.dataType, offset );
|
419 |
+
offset.value += 4;
|
420 |
+
setUint32( dv, 1, offset );
|
421 |
+
setUint32( dv, 1, offset );
|
422 |
+
|
423 |
+
setString( dv, 'B', offset );
|
424 |
+
setUint32( dv, info.dataType, offset );
|
425 |
+
offset.value += 4;
|
426 |
+
setUint32( dv, 1, offset );
|
427 |
+
setUint32( dv, 1, offset );
|
428 |
+
|
429 |
+
setString( dv, 'G', offset );
|
430 |
+
setUint32( dv, info.dataType, offset );
|
431 |
+
offset.value += 4;
|
432 |
+
setUint32( dv, 1, offset );
|
433 |
+
setUint32( dv, 1, offset );
|
434 |
+
|
435 |
+
setString( dv, 'R', offset );
|
436 |
+
setUint32( dv, info.dataType, offset );
|
437 |
+
offset.value += 4;
|
438 |
+
setUint32( dv, 1, offset );
|
439 |
+
setUint32( dv, 1, offset );
|
440 |
+
|
441 |
+
setUint8( dv, 0, offset );
|
442 |
+
|
443 |
+
// null-byte
|
444 |
+
setUint8( dv, 0, offset );
|
445 |
+
|
446 |
+
// = OFFSET TABLE =
|
447 |
+
|
448 |
+
let sum = offset.value + info.numBlocks * 8;
|
449 |
+
|
450 |
+
for ( let i = 0; i < chunks.data.length; ++ i ) {
|
451 |
+
|
452 |
+
setUint64( dv, sum, offset );
|
453 |
+
|
454 |
+
sum += chunks.data[ i ].size + 8;
|
455 |
+
|
456 |
+
}
|
457 |
+
|
458 |
+
}
|
459 |
+
|
460 |
+
function fillData( chunks, info ) {
|
461 |
+
|
462 |
+
const TableSize = info.numBlocks * 8,
|
463 |
+
HeaderSize = 259 + ( 18 * info.numOutputChannels ), // 259 + 18 * chlist
|
464 |
+
offset = { value: HeaderSize + TableSize },
|
465 |
+
outBuffer = new Uint8Array( HeaderSize + TableSize + chunks.totalSize + info.numBlocks * 8 ),
|
466 |
+
dv = new DataView( outBuffer.buffer );
|
467 |
+
|
468 |
+
fillHeader( outBuffer, chunks, info );
|
469 |
+
|
470 |
+
for ( let i = 0; i < chunks.data.length; ++ i ) {
|
471 |
+
|
472 |
+
const data = chunks.data[ i ].dataChunk;
|
473 |
+
const size = chunks.data[ i ].size;
|
474 |
+
|
475 |
+
setUint32( dv, i * info.blockLines, offset );
|
476 |
+
setUint32( dv, size, offset );
|
477 |
+
|
478 |
+
outBuffer.set( data, offset.value );
|
479 |
+
offset.value += size;
|
480 |
+
|
481 |
+
}
|
482 |
+
|
483 |
+
return outBuffer;
|
484 |
+
|
485 |
+
}
|
486 |
+
|
487 |
+
function decodeLinear( dec, r, g, b, a ) {
|
488 |
+
|
489 |
+
dec.r = r;
|
490 |
+
dec.g = g;
|
491 |
+
dec.b = b;
|
492 |
+
dec.a = a;
|
493 |
+
|
494 |
+
}
|
495 |
+
|
496 |
+
// function decodeSRGB( dec, r, g, b, a ) {
|
497 |
+
|
498 |
+
// dec.r = r > 0.04045 ? Math.pow( r * 0.9478672986 + 0.0521327014, 2.4 ) : r * 0.0773993808;
|
499 |
+
// dec.g = g > 0.04045 ? Math.pow( g * 0.9478672986 + 0.0521327014, 2.4 ) : g * 0.0773993808;
|
500 |
+
// dec.b = b > 0.04045 ? Math.pow( b * 0.9478672986 + 0.0521327014, 2.4 ) : b * 0.0773993808;
|
501 |
+
// dec.a = a;
|
502 |
+
|
503 |
+
// }
|
504 |
+
|
505 |
+
|
506 |
+
function setUint8( dv, value, offset ) {
|
507 |
+
|
508 |
+
dv.setUint8( offset.value, value );
|
509 |
+
|
510 |
+
offset.value += 1;
|
511 |
+
|
512 |
+
}
|
513 |
+
|
514 |
+
function setUint32( dv, value, offset ) {
|
515 |
+
|
516 |
+
dv.setUint32( offset.value, value, true );
|
517 |
+
|
518 |
+
offset.value += 4;
|
519 |
+
|
520 |
+
}
|
521 |
+
|
522 |
+
function setFloat16( dv, value, offset ) {
|
523 |
+
|
524 |
+
dv.setUint16( offset.value, DataUtils.toHalfFloat( value ), true );
|
525 |
+
|
526 |
+
offset.value += 2;
|
527 |
+
|
528 |
+
}
|
529 |
+
|
530 |
+
function setFloat32( dv, value, offset ) {
|
531 |
+
|
532 |
+
dv.setFloat32( offset.value, value, true );
|
533 |
+
|
534 |
+
offset.value += 4;
|
535 |
+
|
536 |
+
}
|
537 |
+
|
538 |
+
function setUint64( dv, value, offset ) {
|
539 |
+
|
540 |
+
dv.setBigUint64( offset.value, BigInt( value ), true );
|
541 |
+
|
542 |
+
offset.value += 8;
|
543 |
+
|
544 |
+
}
|
545 |
+
|
546 |
+
function setString( dv, string, offset ) {
|
547 |
+
|
548 |
+
const tmp = textEncoder.encode( string + '\0' );
|
549 |
+
|
550 |
+
for ( let i = 0; i < tmp.length; ++ i ) {
|
551 |
+
|
552 |
+
setUint8( dv, tmp[ i ], offset );
|
553 |
+
|
554 |
+
}
|
555 |
+
|
556 |
+
}
|
557 |
+
|
558 |
+
function decodeFloat16( binary ) {
|
559 |
+
|
560 |
+
const exponent = ( binary & 0x7C00 ) >> 10,
|
561 |
+
fraction = binary & 0x03FF;
|
562 |
+
|
563 |
+
return ( binary >> 15 ? - 1 : 1 ) * (
|
564 |
+
exponent ?
|
565 |
+
(
|
566 |
+
exponent === 0x1F ?
|
567 |
+
fraction ? NaN : Infinity :
|
568 |
+
Math.pow( 2, exponent - 15 ) * ( 1 + fraction / 0x400 )
|
569 |
+
) :
|
570 |
+
6.103515625e-5 * ( fraction / 0x400 )
|
571 |
+
);
|
572 |
+
|
573 |
+
}
|
574 |
+
|
575 |
+
function getFloat16( arr, i ) {
|
576 |
+
|
577 |
+
return decodeFloat16( arr[ i ] );
|
578 |
+
|
579 |
+
}
|
580 |
+
|
581 |
+
function getFloat32( arr, i ) {
|
582 |
+
|
583 |
+
return arr[ i ];
|
584 |
+
|
585 |
+
}
|
586 |
+
|
587 |
+
export { EXRExporter, NO_COMPRESSION, ZIP_COMPRESSION, ZIPS_COMPRESSION };
|
libs/three.js/0.172.0/jsm/exporters/GLTFExporter.js
ADDED
@@ -0,0 +1,3460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BufferAttribute,
|
3 |
+
ClampToEdgeWrapping,
|
4 |
+
Color,
|
5 |
+
DoubleSide,
|
6 |
+
InterpolateDiscrete,
|
7 |
+
InterpolateLinear,
|
8 |
+
NoColorSpace,
|
9 |
+
LinearFilter,
|
10 |
+
LinearMipmapLinearFilter,
|
11 |
+
LinearMipmapNearestFilter,
|
12 |
+
MathUtils,
|
13 |
+
Matrix4,
|
14 |
+
MirroredRepeatWrapping,
|
15 |
+
NearestFilter,
|
16 |
+
NearestMipmapLinearFilter,
|
17 |
+
NearestMipmapNearestFilter,
|
18 |
+
PropertyBinding,
|
19 |
+
RGBAFormat,
|
20 |
+
RepeatWrapping,
|
21 |
+
Scene,
|
22 |
+
Source,
|
23 |
+
SRGBColorSpace,
|
24 |
+
CompressedTexture,
|
25 |
+
Vector3,
|
26 |
+
Quaternion,
|
27 |
+
REVISION
|
28 |
+
} from 'three';
|
29 |
+
|
30 |
+
/**
|
31 |
+
* The KHR_mesh_quantization extension allows these extra attribute component types
|
32 |
+
*
|
33 |
+
* @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md#extending-mesh-attributes
|
34 |
+
*/
|
35 |
+
const KHR_mesh_quantization_ExtraAttrTypes = {
|
36 |
+
POSITION: [
|
37 |
+
'byte',
|
38 |
+
'byte normalized',
|
39 |
+
'unsigned byte',
|
40 |
+
'unsigned byte normalized',
|
41 |
+
'short',
|
42 |
+
'short normalized',
|
43 |
+
'unsigned short',
|
44 |
+
'unsigned short normalized',
|
45 |
+
],
|
46 |
+
NORMAL: [
|
47 |
+
'byte normalized',
|
48 |
+
'short normalized',
|
49 |
+
],
|
50 |
+
TANGENT: [
|
51 |
+
'byte normalized',
|
52 |
+
'short normalized',
|
53 |
+
],
|
54 |
+
TEXCOORD: [
|
55 |
+
'byte',
|
56 |
+
'byte normalized',
|
57 |
+
'unsigned byte',
|
58 |
+
'short',
|
59 |
+
'short normalized',
|
60 |
+
'unsigned short',
|
61 |
+
],
|
62 |
+
};
|
63 |
+
|
64 |
+
|
65 |
+
class GLTFExporter {
|
66 |
+
|
67 |
+
constructor() {
|
68 |
+
|
69 |
+
this.textureUtils = null;
|
70 |
+
|
71 |
+
this.pluginCallbacks = [];
|
72 |
+
|
73 |
+
this.register( function ( writer ) {
|
74 |
+
|
75 |
+
return new GLTFLightExtension( writer );
|
76 |
+
|
77 |
+
} );
|
78 |
+
|
79 |
+
this.register( function ( writer ) {
|
80 |
+
|
81 |
+
return new GLTFMaterialsUnlitExtension( writer );
|
82 |
+
|
83 |
+
} );
|
84 |
+
|
85 |
+
this.register( function ( writer ) {
|
86 |
+
|
87 |
+
return new GLTFMaterialsTransmissionExtension( writer );
|
88 |
+
|
89 |
+
} );
|
90 |
+
|
91 |
+
this.register( function ( writer ) {
|
92 |
+
|
93 |
+
return new GLTFMaterialsVolumeExtension( writer );
|
94 |
+
|
95 |
+
} );
|
96 |
+
|
97 |
+
this.register( function ( writer ) {
|
98 |
+
|
99 |
+
return new GLTFMaterialsIorExtension( writer );
|
100 |
+
|
101 |
+
} );
|
102 |
+
|
103 |
+
this.register( function ( writer ) {
|
104 |
+
|
105 |
+
return new GLTFMaterialsSpecularExtension( writer );
|
106 |
+
|
107 |
+
} );
|
108 |
+
|
109 |
+
this.register( function ( writer ) {
|
110 |
+
|
111 |
+
return new GLTFMaterialsClearcoatExtension( writer );
|
112 |
+
|
113 |
+
} );
|
114 |
+
|
115 |
+
this.register( function ( writer ) {
|
116 |
+
|
117 |
+
return new GLTFMaterialsDispersionExtension( writer );
|
118 |
+
|
119 |
+
} );
|
120 |
+
|
121 |
+
this.register( function ( writer ) {
|
122 |
+
|
123 |
+
return new GLTFMaterialsIridescenceExtension( writer );
|
124 |
+
|
125 |
+
} );
|
126 |
+
|
127 |
+
this.register( function ( writer ) {
|
128 |
+
|
129 |
+
return new GLTFMaterialsSheenExtension( writer );
|
130 |
+
|
131 |
+
} );
|
132 |
+
|
133 |
+
this.register( function ( writer ) {
|
134 |
+
|
135 |
+
return new GLTFMaterialsAnisotropyExtension( writer );
|
136 |
+
|
137 |
+
} );
|
138 |
+
|
139 |
+
this.register( function ( writer ) {
|
140 |
+
|
141 |
+
return new GLTFMaterialsEmissiveStrengthExtension( writer );
|
142 |
+
|
143 |
+
} );
|
144 |
+
|
145 |
+
this.register( function ( writer ) {
|
146 |
+
|
147 |
+
return new GLTFMaterialsBumpExtension( writer );
|
148 |
+
|
149 |
+
} );
|
150 |
+
|
151 |
+
this.register( function ( writer ) {
|
152 |
+
|
153 |
+
return new GLTFMeshGpuInstancing( writer );
|
154 |
+
|
155 |
+
} );
|
156 |
+
|
157 |
+
}
|
158 |
+
|
159 |
+
register( callback ) {
|
160 |
+
|
161 |
+
if ( this.pluginCallbacks.indexOf( callback ) === - 1 ) {
|
162 |
+
|
163 |
+
this.pluginCallbacks.push( callback );
|
164 |
+
|
165 |
+
}
|
166 |
+
|
167 |
+
return this;
|
168 |
+
|
169 |
+
}
|
170 |
+
|
171 |
+
unregister( callback ) {
|
172 |
+
|
173 |
+
if ( this.pluginCallbacks.indexOf( callback ) !== - 1 ) {
|
174 |
+
|
175 |
+
this.pluginCallbacks.splice( this.pluginCallbacks.indexOf( callback ), 1 );
|
176 |
+
|
177 |
+
}
|
178 |
+
|
179 |
+
return this;
|
180 |
+
|
181 |
+
}
|
182 |
+
|
183 |
+
setTextureUtils( utils ) {
|
184 |
+
|
185 |
+
this.textureUtils = utils;
|
186 |
+
|
187 |
+
return this;
|
188 |
+
|
189 |
+
}
|
190 |
+
|
191 |
+
/**
|
192 |
+
* Parse scenes and generate GLTF output
|
193 |
+
*
|
194 |
+
* @param {Scene|THREE.Scenes} input Scene or Array of THREE.Scenes
|
195 |
+
* @param {Function} onDone Callback on completed
|
196 |
+
* @param {Function} onError Callback on errors
|
197 |
+
* @param {Object} options options
|
198 |
+
*/
|
199 |
+
parse( input, onDone, onError, options ) {
|
200 |
+
|
201 |
+
const writer = new GLTFWriter();
|
202 |
+
const plugins = [];
|
203 |
+
|
204 |
+
for ( let i = 0, il = this.pluginCallbacks.length; i < il; i ++ ) {
|
205 |
+
|
206 |
+
plugins.push( this.pluginCallbacks[ i ]( writer ) );
|
207 |
+
|
208 |
+
}
|
209 |
+
|
210 |
+
writer.setPlugins( plugins );
|
211 |
+
writer.setTextureUtils( this.textureUtils );
|
212 |
+
writer.writeAsync( input, onDone, options ).catch( onError );
|
213 |
+
|
214 |
+
}
|
215 |
+
|
216 |
+
parseAsync( input, options ) {
|
217 |
+
|
218 |
+
const scope = this;
|
219 |
+
|
220 |
+
return new Promise( function ( resolve, reject ) {
|
221 |
+
|
222 |
+
scope.parse( input, resolve, reject, options );
|
223 |
+
|
224 |
+
} );
|
225 |
+
|
226 |
+
}
|
227 |
+
|
228 |
+
}
|
229 |
+
|
230 |
+
//------------------------------------------------------------------------------
|
231 |
+
// Constants
|
232 |
+
//------------------------------------------------------------------------------
|
233 |
+
|
234 |
+
const WEBGL_CONSTANTS = {
|
235 |
+
POINTS: 0x0000,
|
236 |
+
LINES: 0x0001,
|
237 |
+
LINE_LOOP: 0x0002,
|
238 |
+
LINE_STRIP: 0x0003,
|
239 |
+
TRIANGLES: 0x0004,
|
240 |
+
TRIANGLE_STRIP: 0x0005,
|
241 |
+
TRIANGLE_FAN: 0x0006,
|
242 |
+
|
243 |
+
BYTE: 0x1400,
|
244 |
+
UNSIGNED_BYTE: 0x1401,
|
245 |
+
SHORT: 0x1402,
|
246 |
+
UNSIGNED_SHORT: 0x1403,
|
247 |
+
INT: 0x1404,
|
248 |
+
UNSIGNED_INT: 0x1405,
|
249 |
+
FLOAT: 0x1406,
|
250 |
+
|
251 |
+
ARRAY_BUFFER: 0x8892,
|
252 |
+
ELEMENT_ARRAY_BUFFER: 0x8893,
|
253 |
+
|
254 |
+
NEAREST: 0x2600,
|
255 |
+
LINEAR: 0x2601,
|
256 |
+
NEAREST_MIPMAP_NEAREST: 0x2700,
|
257 |
+
LINEAR_MIPMAP_NEAREST: 0x2701,
|
258 |
+
NEAREST_MIPMAP_LINEAR: 0x2702,
|
259 |
+
LINEAR_MIPMAP_LINEAR: 0x2703,
|
260 |
+
|
261 |
+
CLAMP_TO_EDGE: 33071,
|
262 |
+
MIRRORED_REPEAT: 33648,
|
263 |
+
REPEAT: 10497
|
264 |
+
};
|
265 |
+
|
266 |
+
const KHR_MESH_QUANTIZATION = 'KHR_mesh_quantization';
|
267 |
+
|
268 |
+
const THREE_TO_WEBGL = {};
|
269 |
+
|
270 |
+
THREE_TO_WEBGL[ NearestFilter ] = WEBGL_CONSTANTS.NEAREST;
|
271 |
+
THREE_TO_WEBGL[ NearestMipmapNearestFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_NEAREST;
|
272 |
+
THREE_TO_WEBGL[ NearestMipmapLinearFilter ] = WEBGL_CONSTANTS.NEAREST_MIPMAP_LINEAR;
|
273 |
+
THREE_TO_WEBGL[ LinearFilter ] = WEBGL_CONSTANTS.LINEAR;
|
274 |
+
THREE_TO_WEBGL[ LinearMipmapNearestFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_NEAREST;
|
275 |
+
THREE_TO_WEBGL[ LinearMipmapLinearFilter ] = WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR;
|
276 |
+
|
277 |
+
THREE_TO_WEBGL[ ClampToEdgeWrapping ] = WEBGL_CONSTANTS.CLAMP_TO_EDGE;
|
278 |
+
THREE_TO_WEBGL[ RepeatWrapping ] = WEBGL_CONSTANTS.REPEAT;
|
279 |
+
THREE_TO_WEBGL[ MirroredRepeatWrapping ] = WEBGL_CONSTANTS.MIRRORED_REPEAT;
|
280 |
+
|
281 |
+
const PATH_PROPERTIES = {
|
282 |
+
scale: 'scale',
|
283 |
+
position: 'translation',
|
284 |
+
quaternion: 'rotation',
|
285 |
+
morphTargetInfluences: 'weights'
|
286 |
+
};
|
287 |
+
|
288 |
+
const DEFAULT_SPECULAR_COLOR = new Color();
|
289 |
+
|
290 |
+
// GLB constants
|
291 |
+
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
|
292 |
+
|
293 |
+
const GLB_HEADER_BYTES = 12;
|
294 |
+
const GLB_HEADER_MAGIC = 0x46546C67;
|
295 |
+
const GLB_VERSION = 2;
|
296 |
+
|
297 |
+
const GLB_CHUNK_PREFIX_BYTES = 8;
|
298 |
+
const GLB_CHUNK_TYPE_JSON = 0x4E4F534A;
|
299 |
+
const GLB_CHUNK_TYPE_BIN = 0x004E4942;
|
300 |
+
|
301 |
+
//------------------------------------------------------------------------------
|
302 |
+
// Utility functions
|
303 |
+
//------------------------------------------------------------------------------
|
304 |
+
|
305 |
+
/**
|
306 |
+
* Compare two arrays
|
307 |
+
* @param {Array} array1 Array 1 to compare
|
308 |
+
* @param {Array} array2 Array 2 to compare
|
309 |
+
* @return {Boolean} Returns true if both arrays are equal
|
310 |
+
*/
|
311 |
+
function equalArray( array1, array2 ) {
|
312 |
+
|
313 |
+
return ( array1.length === array2.length ) && array1.every( function ( element, index ) {
|
314 |
+
|
315 |
+
return element === array2[ index ];
|
316 |
+
|
317 |
+
} );
|
318 |
+
|
319 |
+
}
|
320 |
+
|
321 |
+
/**
|
322 |
+
* Converts a string to an ArrayBuffer.
|
323 |
+
* @param {string} text
|
324 |
+
* @return {ArrayBuffer}
|
325 |
+
*/
|
326 |
+
function stringToArrayBuffer( text ) {
|
327 |
+
|
328 |
+
return new TextEncoder().encode( text ).buffer;
|
329 |
+
|
330 |
+
}
|
331 |
+
|
332 |
+
/**
|
333 |
+
* Is identity matrix
|
334 |
+
*
|
335 |
+
* @param {Matrix4} matrix
|
336 |
+
* @returns {Boolean} Returns true, if parameter is identity matrix
|
337 |
+
*/
|
338 |
+
function isIdentityMatrix( matrix ) {
|
339 |
+
|
340 |
+
return equalArray( matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] );
|
341 |
+
|
342 |
+
}
|
343 |
+
|
344 |
+
/**
|
345 |
+
* Get the min and max vectors from the given attribute
|
346 |
+
* @param {BufferAttribute} attribute Attribute to find the min/max in range from start to start + count
|
347 |
+
* @param {Integer} start
|
348 |
+
* @param {Integer} count
|
349 |
+
* @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
|
350 |
+
*/
|
351 |
+
function getMinMax( attribute, start, count ) {
|
352 |
+
|
353 |
+
const output = {
|
354 |
+
|
355 |
+
min: new Array( attribute.itemSize ).fill( Number.POSITIVE_INFINITY ),
|
356 |
+
max: new Array( attribute.itemSize ).fill( Number.NEGATIVE_INFINITY )
|
357 |
+
|
358 |
+
};
|
359 |
+
|
360 |
+
for ( let i = start; i < start + count; i ++ ) {
|
361 |
+
|
362 |
+
for ( let a = 0; a < attribute.itemSize; a ++ ) {
|
363 |
+
|
364 |
+
let value;
|
365 |
+
|
366 |
+
if ( attribute.itemSize > 4 ) {
|
367 |
+
|
368 |
+
// no support for interleaved data for itemSize > 4
|
369 |
+
|
370 |
+
value = attribute.array[ i * attribute.itemSize + a ];
|
371 |
+
|
372 |
+
} else {
|
373 |
+
|
374 |
+
if ( a === 0 ) value = attribute.getX( i );
|
375 |
+
else if ( a === 1 ) value = attribute.getY( i );
|
376 |
+
else if ( a === 2 ) value = attribute.getZ( i );
|
377 |
+
else if ( a === 3 ) value = attribute.getW( i );
|
378 |
+
|
379 |
+
if ( attribute.normalized === true ) {
|
380 |
+
|
381 |
+
value = MathUtils.normalize( value, attribute.array );
|
382 |
+
|
383 |
+
}
|
384 |
+
|
385 |
+
}
|
386 |
+
|
387 |
+
output.min[ a ] = Math.min( output.min[ a ], value );
|
388 |
+
output.max[ a ] = Math.max( output.max[ a ], value );
|
389 |
+
|
390 |
+
}
|
391 |
+
|
392 |
+
}
|
393 |
+
|
394 |
+
return output;
|
395 |
+
|
396 |
+
}
|
397 |
+
|
398 |
+
/**
|
399 |
+
* Get the required size + padding for a buffer, rounded to the next 4-byte boundary.
|
400 |
+
* https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#data-alignment
|
401 |
+
*
|
402 |
+
* @param {Integer} bufferSize The size the original buffer.
|
403 |
+
* @returns {Integer} new buffer size with required padding.
|
404 |
+
*
|
405 |
+
*/
|
406 |
+
function getPaddedBufferSize( bufferSize ) {
|
407 |
+
|
408 |
+
return Math.ceil( bufferSize / 4 ) * 4;
|
409 |
+
|
410 |
+
}
|
411 |
+
|
412 |
+
/**
|
413 |
+
* Returns a buffer aligned to 4-byte boundary.
|
414 |
+
*
|
415 |
+
* @param {ArrayBuffer} arrayBuffer Buffer to pad
|
416 |
+
* @param {Integer} paddingByte (Optional)
|
417 |
+
* @returns {ArrayBuffer} The same buffer if it's already aligned to 4-byte boundary or a new buffer
|
418 |
+
*/
|
419 |
+
function getPaddedArrayBuffer( arrayBuffer, paddingByte = 0 ) {
|
420 |
+
|
421 |
+
const paddedLength = getPaddedBufferSize( arrayBuffer.byteLength );
|
422 |
+
|
423 |
+
if ( paddedLength !== arrayBuffer.byteLength ) {
|
424 |
+
|
425 |
+
const array = new Uint8Array( paddedLength );
|
426 |
+
array.set( new Uint8Array( arrayBuffer ) );
|
427 |
+
|
428 |
+
if ( paddingByte !== 0 ) {
|
429 |
+
|
430 |
+
for ( let i = arrayBuffer.byteLength; i < paddedLength; i ++ ) {
|
431 |
+
|
432 |
+
array[ i ] = paddingByte;
|
433 |
+
|
434 |
+
}
|
435 |
+
|
436 |
+
}
|
437 |
+
|
438 |
+
return array.buffer;
|
439 |
+
|
440 |
+
}
|
441 |
+
|
442 |
+
return arrayBuffer;
|
443 |
+
|
444 |
+
}
|
445 |
+
|
446 |
+
function getCanvas() {
|
447 |
+
|
448 |
+
if ( typeof document === 'undefined' && typeof OffscreenCanvas !== 'undefined' ) {
|
449 |
+
|
450 |
+
return new OffscreenCanvas( 1, 1 );
|
451 |
+
|
452 |
+
}
|
453 |
+
|
454 |
+
return document.createElement( 'canvas' );
|
455 |
+
|
456 |
+
}
|
457 |
+
|
458 |
+
function getToBlobPromise( canvas, mimeType ) {
|
459 |
+
|
460 |
+
if ( canvas.toBlob !== undefined ) {
|
461 |
+
|
462 |
+
return new Promise( ( resolve ) => canvas.toBlob( resolve, mimeType ) );
|
463 |
+
|
464 |
+
}
|
465 |
+
|
466 |
+
let quality;
|
467 |
+
|
468 |
+
// Blink's implementation of convertToBlob seems to default to a quality level of 100%
|
469 |
+
// Use the Blink default quality levels of toBlob instead so that file sizes are comparable.
|
470 |
+
if ( mimeType === 'image/jpeg' ) {
|
471 |
+
|
472 |
+
quality = 0.92;
|
473 |
+
|
474 |
+
} else if ( mimeType === 'image/webp' ) {
|
475 |
+
|
476 |
+
quality = 0.8;
|
477 |
+
|
478 |
+
}
|
479 |
+
|
480 |
+
return canvas.convertToBlob( {
|
481 |
+
|
482 |
+
type: mimeType,
|
483 |
+
quality: quality
|
484 |
+
|
485 |
+
} );
|
486 |
+
|
487 |
+
}
|
488 |
+
|
489 |
+
/**
|
490 |
+
* Writer
|
491 |
+
*/
|
492 |
+
class GLTFWriter {
|
493 |
+
|
494 |
+
constructor() {
|
495 |
+
|
496 |
+
this.plugins = [];
|
497 |
+
|
498 |
+
this.options = {};
|
499 |
+
this.pending = [];
|
500 |
+
this.buffers = [];
|
501 |
+
|
502 |
+
this.byteOffset = 0;
|
503 |
+
this.buffers = [];
|
504 |
+
this.nodeMap = new Map();
|
505 |
+
this.skins = [];
|
506 |
+
|
507 |
+
this.extensionsUsed = {};
|
508 |
+
this.extensionsRequired = {};
|
509 |
+
|
510 |
+
this.uids = new Map();
|
511 |
+
this.uid = 0;
|
512 |
+
|
513 |
+
this.json = {
|
514 |
+
asset: {
|
515 |
+
version: '2.0',
|
516 |
+
generator: 'THREE.GLTFExporter r' + REVISION
|
517 |
+
}
|
518 |
+
};
|
519 |
+
|
520 |
+
this.cache = {
|
521 |
+
meshes: new Map(),
|
522 |
+
attributes: new Map(),
|
523 |
+
attributesNormalized: new Map(),
|
524 |
+
materials: new Map(),
|
525 |
+
textures: new Map(),
|
526 |
+
images: new Map()
|
527 |
+
};
|
528 |
+
|
529 |
+
this.textureUtils = null;
|
530 |
+
|
531 |
+
}
|
532 |
+
|
533 |
+
setPlugins( plugins ) {
|
534 |
+
|
535 |
+
this.plugins = plugins;
|
536 |
+
|
537 |
+
}
|
538 |
+
|
539 |
+
setTextureUtils( utils ) {
|
540 |
+
|
541 |
+
this.textureUtils = utils;
|
542 |
+
|
543 |
+
}
|
544 |
+
|
545 |
+
/**
|
546 |
+
* Parse scenes and generate GLTF output
|
547 |
+
*
|
548 |
+
* @param {Scene|THREE.Scenes} input Scene or Array of THREE.Scenes
|
549 |
+
* @param {Function} onDone Callback on completed
|
550 |
+
* @param {Object} options options
|
551 |
+
*/
|
552 |
+
async writeAsync( input, onDone, options = {} ) {
|
553 |
+
|
554 |
+
this.options = Object.assign( {
|
555 |
+
// default options
|
556 |
+
binary: false,
|
557 |
+
trs: false,
|
558 |
+
onlyVisible: true,
|
559 |
+
maxTextureSize: Infinity,
|
560 |
+
animations: [],
|
561 |
+
includeCustomExtensions: false
|
562 |
+
}, options );
|
563 |
+
|
564 |
+
if ( this.options.animations.length > 0 ) {
|
565 |
+
|
566 |
+
// Only TRS properties, and not matrices, may be targeted by animation.
|
567 |
+
this.options.trs = true;
|
568 |
+
|
569 |
+
}
|
570 |
+
|
571 |
+
await this.processInputAsync( input );
|
572 |
+
|
573 |
+
await Promise.all( this.pending );
|
574 |
+
|
575 |
+
const writer = this;
|
576 |
+
const buffers = writer.buffers;
|
577 |
+
const json = writer.json;
|
578 |
+
options = writer.options;
|
579 |
+
|
580 |
+
const extensionsUsed = writer.extensionsUsed;
|
581 |
+
const extensionsRequired = writer.extensionsRequired;
|
582 |
+
|
583 |
+
// Merge buffers.
|
584 |
+
const blob = new Blob( buffers, { type: 'application/octet-stream' } );
|
585 |
+
|
586 |
+
// Declare extensions.
|
587 |
+
const extensionsUsedList = Object.keys( extensionsUsed );
|
588 |
+
const extensionsRequiredList = Object.keys( extensionsRequired );
|
589 |
+
|
590 |
+
if ( extensionsUsedList.length > 0 ) json.extensionsUsed = extensionsUsedList;
|
591 |
+
if ( extensionsRequiredList.length > 0 ) json.extensionsRequired = extensionsRequiredList;
|
592 |
+
|
593 |
+
// Update bytelength of the single buffer.
|
594 |
+
if ( json.buffers && json.buffers.length > 0 ) json.buffers[ 0 ].byteLength = blob.size;
|
595 |
+
|
596 |
+
if ( options.binary === true ) {
|
597 |
+
|
598 |
+
// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#glb-file-format-specification
|
599 |
+
|
600 |
+
const reader = new FileReader();
|
601 |
+
reader.readAsArrayBuffer( blob );
|
602 |
+
reader.onloadend = function () {
|
603 |
+
|
604 |
+
// Binary chunk.
|
605 |
+
const binaryChunk = getPaddedArrayBuffer( reader.result );
|
606 |
+
const binaryChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
|
607 |
+
binaryChunkPrefix.setUint32( 0, binaryChunk.byteLength, true );
|
608 |
+
binaryChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_BIN, true );
|
609 |
+
|
610 |
+
// JSON chunk.
|
611 |
+
const jsonChunk = getPaddedArrayBuffer( stringToArrayBuffer( JSON.stringify( json ) ), 0x20 );
|
612 |
+
const jsonChunkPrefix = new DataView( new ArrayBuffer( GLB_CHUNK_PREFIX_BYTES ) );
|
613 |
+
jsonChunkPrefix.setUint32( 0, jsonChunk.byteLength, true );
|
614 |
+
jsonChunkPrefix.setUint32( 4, GLB_CHUNK_TYPE_JSON, true );
|
615 |
+
|
616 |
+
// GLB header.
|
617 |
+
const header = new ArrayBuffer( GLB_HEADER_BYTES );
|
618 |
+
const headerView = new DataView( header );
|
619 |
+
headerView.setUint32( 0, GLB_HEADER_MAGIC, true );
|
620 |
+
headerView.setUint32( 4, GLB_VERSION, true );
|
621 |
+
const totalByteLength = GLB_HEADER_BYTES
|
622 |
+
+ jsonChunkPrefix.byteLength + jsonChunk.byteLength
|
623 |
+
+ binaryChunkPrefix.byteLength + binaryChunk.byteLength;
|
624 |
+
headerView.setUint32( 8, totalByteLength, true );
|
625 |
+
|
626 |
+
const glbBlob = new Blob( [
|
627 |
+
header,
|
628 |
+
jsonChunkPrefix,
|
629 |
+
jsonChunk,
|
630 |
+
binaryChunkPrefix,
|
631 |
+
binaryChunk
|
632 |
+
], { type: 'application/octet-stream' } );
|
633 |
+
|
634 |
+
const glbReader = new FileReader();
|
635 |
+
glbReader.readAsArrayBuffer( glbBlob );
|
636 |
+
glbReader.onloadend = function () {
|
637 |
+
|
638 |
+
onDone( glbReader.result );
|
639 |
+
|
640 |
+
};
|
641 |
+
|
642 |
+
};
|
643 |
+
|
644 |
+
} else {
|
645 |
+
|
646 |
+
if ( json.buffers && json.buffers.length > 0 ) {
|
647 |
+
|
648 |
+
const reader = new FileReader();
|
649 |
+
reader.readAsDataURL( blob );
|
650 |
+
reader.onloadend = function () {
|
651 |
+
|
652 |
+
const base64data = reader.result;
|
653 |
+
json.buffers[ 0 ].uri = base64data;
|
654 |
+
onDone( json );
|
655 |
+
|
656 |
+
};
|
657 |
+
|
658 |
+
} else {
|
659 |
+
|
660 |
+
onDone( json );
|
661 |
+
|
662 |
+
}
|
663 |
+
|
664 |
+
}
|
665 |
+
|
666 |
+
|
667 |
+
}
|
668 |
+
|
669 |
+
/**
|
670 |
+
* Serializes a userData.
|
671 |
+
*
|
672 |
+
* @param {THREE.Object3D|THREE.Material} object
|
673 |
+
* @param {Object} objectDef
|
674 |
+
*/
|
675 |
+
serializeUserData( object, objectDef ) {
|
676 |
+
|
677 |
+
if ( Object.keys( object.userData ).length === 0 ) return;
|
678 |
+
|
679 |
+
const options = this.options;
|
680 |
+
const extensionsUsed = this.extensionsUsed;
|
681 |
+
|
682 |
+
try {
|
683 |
+
|
684 |
+
const json = JSON.parse( JSON.stringify( object.userData ) );
|
685 |
+
|
686 |
+
if ( options.includeCustomExtensions && json.gltfExtensions ) {
|
687 |
+
|
688 |
+
if ( objectDef.extensions === undefined ) objectDef.extensions = {};
|
689 |
+
|
690 |
+
for ( const extensionName in json.gltfExtensions ) {
|
691 |
+
|
692 |
+
objectDef.extensions[ extensionName ] = json.gltfExtensions[ extensionName ];
|
693 |
+
extensionsUsed[ extensionName ] = true;
|
694 |
+
|
695 |
+
}
|
696 |
+
|
697 |
+
delete json.gltfExtensions;
|
698 |
+
|
699 |
+
}
|
700 |
+
|
701 |
+
if ( Object.keys( json ).length > 0 ) objectDef.extras = json;
|
702 |
+
|
703 |
+
} catch ( error ) {
|
704 |
+
|
705 |
+
console.warn( 'THREE.GLTFExporter: userData of \'' + object.name + '\' ' +
|
706 |
+
'won\'t be serialized because of JSON.stringify error - ' + error.message );
|
707 |
+
|
708 |
+
}
|
709 |
+
|
710 |
+
}
|
711 |
+
|
712 |
+
/**
|
713 |
+
* Returns ids for buffer attributes.
|
714 |
+
*
|
715 |
+
* @param {Object} attribute
|
716 |
+
* @param {boolean} [isRelativeCopy=false]
|
717 |
+
* @return {Integer}
|
718 |
+
*/
|
719 |
+
getUID( attribute, isRelativeCopy = false ) {
|
720 |
+
|
721 |
+
if ( this.uids.has( attribute ) === false ) {
|
722 |
+
|
723 |
+
const uids = new Map();
|
724 |
+
|
725 |
+
uids.set( true, this.uid ++ );
|
726 |
+
uids.set( false, this.uid ++ );
|
727 |
+
|
728 |
+
this.uids.set( attribute, uids );
|
729 |
+
|
730 |
+
}
|
731 |
+
|
732 |
+
const uids = this.uids.get( attribute );
|
733 |
+
|
734 |
+
return uids.get( isRelativeCopy );
|
735 |
+
|
736 |
+
}
|
737 |
+
|
738 |
+
/**
|
739 |
+
* Checks if normal attribute values are normalized.
|
740 |
+
*
|
741 |
+
* @param {BufferAttribute} normal
|
742 |
+
* @returns {Boolean}
|
743 |
+
*/
|
744 |
+
isNormalizedNormalAttribute( normal ) {
|
745 |
+
|
746 |
+
const cache = this.cache;
|
747 |
+
|
748 |
+
if ( cache.attributesNormalized.has( normal ) ) return false;
|
749 |
+
|
750 |
+
const v = new Vector3();
|
751 |
+
|
752 |
+
for ( let i = 0, il = normal.count; i < il; i ++ ) {
|
753 |
+
|
754 |
+
// 0.0005 is from glTF-validator
|
755 |
+
if ( Math.abs( v.fromBufferAttribute( normal, i ).length() - 1.0 ) > 0.0005 ) return false;
|
756 |
+
|
757 |
+
}
|
758 |
+
|
759 |
+
return true;
|
760 |
+
|
761 |
+
}
|
762 |
+
|
763 |
+
/**
|
764 |
+
* Creates normalized normal buffer attribute.
|
765 |
+
*
|
766 |
+
* @param {BufferAttribute} normal
|
767 |
+
* @returns {BufferAttribute}
|
768 |
+
*
|
769 |
+
*/
|
770 |
+
createNormalizedNormalAttribute( normal ) {
|
771 |
+
|
772 |
+
const cache = this.cache;
|
773 |
+
|
774 |
+
if ( cache.attributesNormalized.has( normal ) ) return cache.attributesNormalized.get( normal );
|
775 |
+
|
776 |
+
const attribute = normal.clone();
|
777 |
+
const v = new Vector3();
|
778 |
+
|
779 |
+
for ( let i = 0, il = attribute.count; i < il; i ++ ) {
|
780 |
+
|
781 |
+
v.fromBufferAttribute( attribute, i );
|
782 |
+
|
783 |
+
if ( v.x === 0 && v.y === 0 && v.z === 0 ) {
|
784 |
+
|
785 |
+
// if values can't be normalized set (1, 0, 0)
|
786 |
+
v.setX( 1.0 );
|
787 |
+
|
788 |
+
} else {
|
789 |
+
|
790 |
+
v.normalize();
|
791 |
+
|
792 |
+
}
|
793 |
+
|
794 |
+
attribute.setXYZ( i, v.x, v.y, v.z );
|
795 |
+
|
796 |
+
}
|
797 |
+
|
798 |
+
cache.attributesNormalized.set( normal, attribute );
|
799 |
+
|
800 |
+
return attribute;
|
801 |
+
|
802 |
+
}
|
803 |
+
|
804 |
+
/**
|
805 |
+
* Applies a texture transform, if present, to the map definition. Requires
|
806 |
+
* the KHR_texture_transform extension.
|
807 |
+
*
|
808 |
+
* @param {Object} mapDef
|
809 |
+
* @param {THREE.Texture} texture
|
810 |
+
*/
|
811 |
+
applyTextureTransform( mapDef, texture ) {
|
812 |
+
|
813 |
+
let didTransform = false;
|
814 |
+
const transformDef = {};
|
815 |
+
|
816 |
+
if ( texture.offset.x !== 0 || texture.offset.y !== 0 ) {
|
817 |
+
|
818 |
+
transformDef.offset = texture.offset.toArray();
|
819 |
+
didTransform = true;
|
820 |
+
|
821 |
+
}
|
822 |
+
|
823 |
+
if ( texture.rotation !== 0 ) {
|
824 |
+
|
825 |
+
transformDef.rotation = texture.rotation;
|
826 |
+
didTransform = true;
|
827 |
+
|
828 |
+
}
|
829 |
+
|
830 |
+
if ( texture.repeat.x !== 1 || texture.repeat.y !== 1 ) {
|
831 |
+
|
832 |
+
transformDef.scale = texture.repeat.toArray();
|
833 |
+
didTransform = true;
|
834 |
+
|
835 |
+
}
|
836 |
+
|
837 |
+
if ( didTransform ) {
|
838 |
+
|
839 |
+
mapDef.extensions = mapDef.extensions || {};
|
840 |
+
mapDef.extensions[ 'KHR_texture_transform' ] = transformDef;
|
841 |
+
this.extensionsUsed[ 'KHR_texture_transform' ] = true;
|
842 |
+
|
843 |
+
}
|
844 |
+
|
845 |
+
}
|
846 |
+
|
847 |
+
async buildMetalRoughTextureAsync( metalnessMap, roughnessMap ) {
|
848 |
+
|
849 |
+
if ( metalnessMap === roughnessMap ) return metalnessMap;
|
850 |
+
|
851 |
+
function getEncodingConversion( map ) {
|
852 |
+
|
853 |
+
if ( map.colorSpace === SRGBColorSpace ) {
|
854 |
+
|
855 |
+
return function SRGBToLinear( c ) {
|
856 |
+
|
857 |
+
return ( c < 0.04045 ) ? c * 0.0773993808 : Math.pow( c * 0.9478672986 + 0.0521327014, 2.4 );
|
858 |
+
|
859 |
+
};
|
860 |
+
|
861 |
+
}
|
862 |
+
|
863 |
+
return function LinearToLinear( c ) {
|
864 |
+
|
865 |
+
return c;
|
866 |
+
|
867 |
+
};
|
868 |
+
|
869 |
+
}
|
870 |
+
|
871 |
+
if ( metalnessMap instanceof CompressedTexture ) {
|
872 |
+
|
873 |
+
metalnessMap = await this.decompressTextureAsync( metalnessMap );
|
874 |
+
|
875 |
+
}
|
876 |
+
|
877 |
+
if ( roughnessMap instanceof CompressedTexture ) {
|
878 |
+
|
879 |
+
roughnessMap = await this.decompressTextureAsync( roughnessMap );
|
880 |
+
|
881 |
+
}
|
882 |
+
|
883 |
+
const metalness = metalnessMap ? metalnessMap.image : null;
|
884 |
+
const roughness = roughnessMap ? roughnessMap.image : null;
|
885 |
+
|
886 |
+
const width = Math.max( metalness ? metalness.width : 0, roughness ? roughness.width : 0 );
|
887 |
+
const height = Math.max( metalness ? metalness.height : 0, roughness ? roughness.height : 0 );
|
888 |
+
|
889 |
+
const canvas = getCanvas();
|
890 |
+
canvas.width = width;
|
891 |
+
canvas.height = height;
|
892 |
+
|
893 |
+
const context = canvas.getContext( '2d', {
|
894 |
+
willReadFrequently: true,
|
895 |
+
} );
|
896 |
+
context.fillStyle = '#00ffff';
|
897 |
+
context.fillRect( 0, 0, width, height );
|
898 |
+
|
899 |
+
const composite = context.getImageData( 0, 0, width, height );
|
900 |
+
|
901 |
+
if ( metalness ) {
|
902 |
+
|
903 |
+
context.drawImage( metalness, 0, 0, width, height );
|
904 |
+
|
905 |
+
const convert = getEncodingConversion( metalnessMap );
|
906 |
+
const data = context.getImageData( 0, 0, width, height ).data;
|
907 |
+
|
908 |
+
for ( let i = 2; i < data.length; i += 4 ) {
|
909 |
+
|
910 |
+
composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
|
911 |
+
|
912 |
+
}
|
913 |
+
|
914 |
+
}
|
915 |
+
|
916 |
+
if ( roughness ) {
|
917 |
+
|
918 |
+
context.drawImage( roughness, 0, 0, width, height );
|
919 |
+
|
920 |
+
const convert = getEncodingConversion( roughnessMap );
|
921 |
+
const data = context.getImageData( 0, 0, width, height ).data;
|
922 |
+
|
923 |
+
for ( let i = 1; i < data.length; i += 4 ) {
|
924 |
+
|
925 |
+
composite.data[ i ] = convert( data[ i ] / 256 ) * 256;
|
926 |
+
|
927 |
+
}
|
928 |
+
|
929 |
+
}
|
930 |
+
|
931 |
+
context.putImageData( composite, 0, 0 );
|
932 |
+
|
933 |
+
//
|
934 |
+
|
935 |
+
const reference = metalnessMap || roughnessMap;
|
936 |
+
|
937 |
+
const texture = reference.clone();
|
938 |
+
|
939 |
+
texture.source = new Source( canvas );
|
940 |
+
texture.colorSpace = NoColorSpace;
|
941 |
+
texture.channel = ( metalnessMap || roughnessMap ).channel;
|
942 |
+
|
943 |
+
if ( metalnessMap && roughnessMap && metalnessMap.channel !== roughnessMap.channel ) {
|
944 |
+
|
945 |
+
console.warn( 'THREE.GLTFExporter: UV channels for metalnessMap and roughnessMap textures must match.' );
|
946 |
+
|
947 |
+
}
|
948 |
+
|
949 |
+
console.warn( 'THREE.GLTFExporter: Merged metalnessMap and roughnessMap textures.' );
|
950 |
+
|
951 |
+
return texture;
|
952 |
+
|
953 |
+
}
|
954 |
+
|
955 |
+
|
956 |
+
async decompressTextureAsync( texture, maxTextureSize = Infinity ) {
|
957 |
+
|
958 |
+
if ( this.textureUtils === null ) {
|
959 |
+
|
960 |
+
throw new Error( 'THREE.GLTFExporter: setTextureUtils() must be called to process compressed textures.' );
|
961 |
+
|
962 |
+
}
|
963 |
+
|
964 |
+
return await this.textureUtils.decompress( texture, maxTextureSize );
|
965 |
+
|
966 |
+
}
|
967 |
+
|
968 |
+
/**
|
969 |
+
* Process a buffer to append to the default one.
|
970 |
+
* @param {ArrayBuffer} buffer
|
971 |
+
* @return {Integer}
|
972 |
+
*/
|
973 |
+
processBuffer( buffer ) {
|
974 |
+
|
975 |
+
const json = this.json;
|
976 |
+
const buffers = this.buffers;
|
977 |
+
|
978 |
+
if ( ! json.buffers ) json.buffers = [ { byteLength: 0 } ];
|
979 |
+
|
980 |
+
// All buffers are merged before export.
|
981 |
+
buffers.push( buffer );
|
982 |
+
|
983 |
+
return 0;
|
984 |
+
|
985 |
+
}
|
986 |
+
|
987 |
+
/**
|
988 |
+
* Process and generate a BufferView
|
989 |
+
* @param {BufferAttribute} attribute
|
990 |
+
* @param {number} componentType
|
991 |
+
* @param {number} start
|
992 |
+
* @param {number} count
|
993 |
+
* @param {number} target (Optional) Target usage of the BufferView
|
994 |
+
* @return {Object}
|
995 |
+
*/
|
996 |
+
processBufferView( attribute, componentType, start, count, target ) {
|
997 |
+
|
998 |
+
const json = this.json;
|
999 |
+
|
1000 |
+
if ( ! json.bufferViews ) json.bufferViews = [];
|
1001 |
+
|
1002 |
+
// Create a new dataview and dump the attribute's array into it
|
1003 |
+
|
1004 |
+
let componentSize;
|
1005 |
+
|
1006 |
+
switch ( componentType ) {
|
1007 |
+
|
1008 |
+
case WEBGL_CONSTANTS.BYTE:
|
1009 |
+
case WEBGL_CONSTANTS.UNSIGNED_BYTE:
|
1010 |
+
|
1011 |
+
componentSize = 1;
|
1012 |
+
|
1013 |
+
break;
|
1014 |
+
|
1015 |
+
case WEBGL_CONSTANTS.SHORT:
|
1016 |
+
case WEBGL_CONSTANTS.UNSIGNED_SHORT:
|
1017 |
+
|
1018 |
+
componentSize = 2;
|
1019 |
+
|
1020 |
+
break;
|
1021 |
+
|
1022 |
+
default:
|
1023 |
+
|
1024 |
+
componentSize = 4;
|
1025 |
+
|
1026 |
+
}
|
1027 |
+
|
1028 |
+
let byteStride = attribute.itemSize * componentSize;
|
1029 |
+
|
1030 |
+
if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {
|
1031 |
+
|
1032 |
+
// Each element of a vertex attribute MUST be aligned to 4-byte boundaries
|
1033 |
+
// inside a bufferView
|
1034 |
+
byteStride = Math.ceil( byteStride / 4 ) * 4;
|
1035 |
+
|
1036 |
+
}
|
1037 |
+
|
1038 |
+
const byteLength = getPaddedBufferSize( count * byteStride );
|
1039 |
+
const dataView = new DataView( new ArrayBuffer( byteLength ) );
|
1040 |
+
let offset = 0;
|
1041 |
+
|
1042 |
+
for ( let i = start; i < start + count; i ++ ) {
|
1043 |
+
|
1044 |
+
for ( let a = 0; a < attribute.itemSize; a ++ ) {
|
1045 |
+
|
1046 |
+
let value;
|
1047 |
+
|
1048 |
+
if ( attribute.itemSize > 4 ) {
|
1049 |
+
|
1050 |
+
// no support for interleaved data for itemSize > 4
|
1051 |
+
|
1052 |
+
value = attribute.array[ i * attribute.itemSize + a ];
|
1053 |
+
|
1054 |
+
} else {
|
1055 |
+
|
1056 |
+
if ( a === 0 ) value = attribute.getX( i );
|
1057 |
+
else if ( a === 1 ) value = attribute.getY( i );
|
1058 |
+
else if ( a === 2 ) value = attribute.getZ( i );
|
1059 |
+
else if ( a === 3 ) value = attribute.getW( i );
|
1060 |
+
|
1061 |
+
if ( attribute.normalized === true ) {
|
1062 |
+
|
1063 |
+
value = MathUtils.normalize( value, attribute.array );
|
1064 |
+
|
1065 |
+
}
|
1066 |
+
|
1067 |
+
}
|
1068 |
+
|
1069 |
+
if ( componentType === WEBGL_CONSTANTS.FLOAT ) {
|
1070 |
+
|
1071 |
+
dataView.setFloat32( offset, value, true );
|
1072 |
+
|
1073 |
+
} else if ( componentType === WEBGL_CONSTANTS.INT ) {
|
1074 |
+
|
1075 |
+
dataView.setInt32( offset, value, true );
|
1076 |
+
|
1077 |
+
} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_INT ) {
|
1078 |
+
|
1079 |
+
dataView.setUint32( offset, value, true );
|
1080 |
+
|
1081 |
+
} else if ( componentType === WEBGL_CONSTANTS.SHORT ) {
|
1082 |
+
|
1083 |
+
dataView.setInt16( offset, value, true );
|
1084 |
+
|
1085 |
+
} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_SHORT ) {
|
1086 |
+
|
1087 |
+
dataView.setUint16( offset, value, true );
|
1088 |
+
|
1089 |
+
} else if ( componentType === WEBGL_CONSTANTS.BYTE ) {
|
1090 |
+
|
1091 |
+
dataView.setInt8( offset, value );
|
1092 |
+
|
1093 |
+
} else if ( componentType === WEBGL_CONSTANTS.UNSIGNED_BYTE ) {
|
1094 |
+
|
1095 |
+
dataView.setUint8( offset, value );
|
1096 |
+
|
1097 |
+
}
|
1098 |
+
|
1099 |
+
offset += componentSize;
|
1100 |
+
|
1101 |
+
}
|
1102 |
+
|
1103 |
+
if ( ( offset % byteStride ) !== 0 ) {
|
1104 |
+
|
1105 |
+
offset += byteStride - ( offset % byteStride );
|
1106 |
+
|
1107 |
+
}
|
1108 |
+
|
1109 |
+
}
|
1110 |
+
|
1111 |
+
const bufferViewDef = {
|
1112 |
+
|
1113 |
+
buffer: this.processBuffer( dataView.buffer ),
|
1114 |
+
byteOffset: this.byteOffset,
|
1115 |
+
byteLength: byteLength
|
1116 |
+
|
1117 |
+
};
|
1118 |
+
|
1119 |
+
if ( target !== undefined ) bufferViewDef.target = target;
|
1120 |
+
|
1121 |
+
if ( target === WEBGL_CONSTANTS.ARRAY_BUFFER ) {
|
1122 |
+
|
1123 |
+
// Only define byteStride for vertex attributes.
|
1124 |
+
bufferViewDef.byteStride = byteStride;
|
1125 |
+
|
1126 |
+
}
|
1127 |
+
|
1128 |
+
this.byteOffset += byteLength;
|
1129 |
+
|
1130 |
+
json.bufferViews.push( bufferViewDef );
|
1131 |
+
|
1132 |
+
// @TODO Merge bufferViews where possible.
|
1133 |
+
const output = {
|
1134 |
+
|
1135 |
+
id: json.bufferViews.length - 1,
|
1136 |
+
byteLength: 0
|
1137 |
+
|
1138 |
+
};
|
1139 |
+
|
1140 |
+
return output;
|
1141 |
+
|
1142 |
+
}
|
1143 |
+
|
1144 |
+
/**
|
1145 |
+
* Process and generate a BufferView from an image Blob.
|
1146 |
+
* @param {Blob} blob
|
1147 |
+
* @return {Promise<Integer>}
|
1148 |
+
*/
|
1149 |
+
processBufferViewImage( blob ) {
|
1150 |
+
|
1151 |
+
const writer = this;
|
1152 |
+
const json = writer.json;
|
1153 |
+
|
1154 |
+
if ( ! json.bufferViews ) json.bufferViews = [];
|
1155 |
+
|
1156 |
+
return new Promise( function ( resolve ) {
|
1157 |
+
|
1158 |
+
const reader = new FileReader();
|
1159 |
+
reader.readAsArrayBuffer( blob );
|
1160 |
+
reader.onloadend = function () {
|
1161 |
+
|
1162 |
+
const buffer = getPaddedArrayBuffer( reader.result );
|
1163 |
+
|
1164 |
+
const bufferViewDef = {
|
1165 |
+
buffer: writer.processBuffer( buffer ),
|
1166 |
+
byteOffset: writer.byteOffset,
|
1167 |
+
byteLength: buffer.byteLength
|
1168 |
+
};
|
1169 |
+
|
1170 |
+
writer.byteOffset += buffer.byteLength;
|
1171 |
+
resolve( json.bufferViews.push( bufferViewDef ) - 1 );
|
1172 |
+
|
1173 |
+
};
|
1174 |
+
|
1175 |
+
} );
|
1176 |
+
|
1177 |
+
}
|
1178 |
+
|
1179 |
+
/**
|
1180 |
+
* Process attribute to generate an accessor
|
1181 |
+
* @param {BufferAttribute} attribute Attribute to process
|
1182 |
+
* @param {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
|
1183 |
+
* @param {Integer} start (Optional)
|
1184 |
+
* @param {Integer} count (Optional)
|
1185 |
+
* @return {Integer|null} Index of the processed accessor on the "accessors" array
|
1186 |
+
*/
|
1187 |
+
processAccessor( attribute, geometry, start, count ) {
|
1188 |
+
|
1189 |
+
const json = this.json;
|
1190 |
+
|
1191 |
+
const types = {
|
1192 |
+
|
1193 |
+
1: 'SCALAR',
|
1194 |
+
2: 'VEC2',
|
1195 |
+
3: 'VEC3',
|
1196 |
+
4: 'VEC4',
|
1197 |
+
9: 'MAT3',
|
1198 |
+
16: 'MAT4'
|
1199 |
+
|
1200 |
+
};
|
1201 |
+
|
1202 |
+
let componentType;
|
1203 |
+
|
1204 |
+
// Detect the component type of the attribute array
|
1205 |
+
if ( attribute.array.constructor === Float32Array ) {
|
1206 |
+
|
1207 |
+
componentType = WEBGL_CONSTANTS.FLOAT;
|
1208 |
+
|
1209 |
+
} else if ( attribute.array.constructor === Int32Array ) {
|
1210 |
+
|
1211 |
+
componentType = WEBGL_CONSTANTS.INT;
|
1212 |
+
|
1213 |
+
} else if ( attribute.array.constructor === Uint32Array ) {
|
1214 |
+
|
1215 |
+
componentType = WEBGL_CONSTANTS.UNSIGNED_INT;
|
1216 |
+
|
1217 |
+
} else if ( attribute.array.constructor === Int16Array ) {
|
1218 |
+
|
1219 |
+
componentType = WEBGL_CONSTANTS.SHORT;
|
1220 |
+
|
1221 |
+
} else if ( attribute.array.constructor === Uint16Array ) {
|
1222 |
+
|
1223 |
+
componentType = WEBGL_CONSTANTS.UNSIGNED_SHORT;
|
1224 |
+
|
1225 |
+
} else if ( attribute.array.constructor === Int8Array ) {
|
1226 |
+
|
1227 |
+
componentType = WEBGL_CONSTANTS.BYTE;
|
1228 |
+
|
1229 |
+
} else if ( attribute.array.constructor === Uint8Array ) {
|
1230 |
+
|
1231 |
+
componentType = WEBGL_CONSTANTS.UNSIGNED_BYTE;
|
1232 |
+
|
1233 |
+
} else {
|
1234 |
+
|
1235 |
+
throw new Error( 'THREE.GLTFExporter: Unsupported bufferAttribute component type: ' + attribute.array.constructor.name );
|
1236 |
+
|
1237 |
+
}
|
1238 |
+
|
1239 |
+
if ( start === undefined ) start = 0;
|
1240 |
+
if ( count === undefined || count === Infinity ) count = attribute.count;
|
1241 |
+
|
1242 |
+
// Skip creating an accessor if the attribute doesn't have data to export
|
1243 |
+
if ( count === 0 ) return null;
|
1244 |
+
|
1245 |
+
const minMax = getMinMax( attribute, start, count );
|
1246 |
+
let bufferViewTarget;
|
1247 |
+
|
1248 |
+
// If geometry isn't provided, don't infer the target usage of the bufferView. For
|
1249 |
+
// animation samplers, target must not be set.
|
1250 |
+
if ( geometry !== undefined ) {
|
1251 |
+
|
1252 |
+
bufferViewTarget = attribute === geometry.index ? WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER : WEBGL_CONSTANTS.ARRAY_BUFFER;
|
1253 |
+
|
1254 |
+
}
|
1255 |
+
|
1256 |
+
const bufferView = this.processBufferView( attribute, componentType, start, count, bufferViewTarget );
|
1257 |
+
|
1258 |
+
const accessorDef = {
|
1259 |
+
|
1260 |
+
bufferView: bufferView.id,
|
1261 |
+
byteOffset: bufferView.byteOffset,
|
1262 |
+
componentType: componentType,
|
1263 |
+
count: count,
|
1264 |
+
max: minMax.max,
|
1265 |
+
min: minMax.min,
|
1266 |
+
type: types[ attribute.itemSize ]
|
1267 |
+
|
1268 |
+
};
|
1269 |
+
|
1270 |
+
if ( attribute.normalized === true ) accessorDef.normalized = true;
|
1271 |
+
if ( ! json.accessors ) json.accessors = [];
|
1272 |
+
|
1273 |
+
return json.accessors.push( accessorDef ) - 1;
|
1274 |
+
|
1275 |
+
}
|
1276 |
+
|
1277 |
+
/**
|
1278 |
+
* Process image
|
1279 |
+
* @param {Image} image to process
|
1280 |
+
* @param {Integer} format of the image (RGBAFormat)
|
1281 |
+
* @param {Boolean} flipY before writing out the image
|
1282 |
+
* @param {String} mimeType export format
|
1283 |
+
* @return {Integer} Index of the processed texture in the "images" array
|
1284 |
+
*/
|
1285 |
+
processImage( image, format, flipY, mimeType = 'image/png' ) {
|
1286 |
+
|
1287 |
+
if ( image !== null ) {
|
1288 |
+
|
1289 |
+
const writer = this;
|
1290 |
+
const cache = writer.cache;
|
1291 |
+
const json = writer.json;
|
1292 |
+
const options = writer.options;
|
1293 |
+
const pending = writer.pending;
|
1294 |
+
|
1295 |
+
if ( ! cache.images.has( image ) ) cache.images.set( image, {} );
|
1296 |
+
|
1297 |
+
const cachedImages = cache.images.get( image );
|
1298 |
+
|
1299 |
+
const key = mimeType + ':flipY/' + flipY.toString();
|
1300 |
+
|
1301 |
+
if ( cachedImages[ key ] !== undefined ) return cachedImages[ key ];
|
1302 |
+
|
1303 |
+
if ( ! json.images ) json.images = [];
|
1304 |
+
|
1305 |
+
const imageDef = { mimeType: mimeType };
|
1306 |
+
|
1307 |
+
const canvas = getCanvas();
|
1308 |
+
|
1309 |
+
canvas.width = Math.min( image.width, options.maxTextureSize );
|
1310 |
+
canvas.height = Math.min( image.height, options.maxTextureSize );
|
1311 |
+
|
1312 |
+
const ctx = canvas.getContext( '2d', {
|
1313 |
+
willReadFrequently: true,
|
1314 |
+
} );
|
1315 |
+
|
1316 |
+
if ( flipY === true ) {
|
1317 |
+
|
1318 |
+
ctx.translate( 0, canvas.height );
|
1319 |
+
ctx.scale( 1, - 1 );
|
1320 |
+
|
1321 |
+
}
|
1322 |
+
|
1323 |
+
if ( image.data !== undefined ) { // THREE.DataTexture
|
1324 |
+
|
1325 |
+
if ( format !== RGBAFormat ) {
|
1326 |
+
|
1327 |
+
console.error( 'GLTFExporter: Only RGBAFormat is supported.', format );
|
1328 |
+
|
1329 |
+
}
|
1330 |
+
|
1331 |
+
if ( image.width > options.maxTextureSize || image.height > options.maxTextureSize ) {
|
1332 |
+
|
1333 |
+
console.warn( 'GLTFExporter: Image size is bigger than maxTextureSize', image );
|
1334 |
+
|
1335 |
+
}
|
1336 |
+
|
1337 |
+
const data = new Uint8ClampedArray( image.height * image.width * 4 );
|
1338 |
+
|
1339 |
+
for ( let i = 0; i < data.length; i += 4 ) {
|
1340 |
+
|
1341 |
+
data[ i + 0 ] = image.data[ i + 0 ];
|
1342 |
+
data[ i + 1 ] = image.data[ i + 1 ];
|
1343 |
+
data[ i + 2 ] = image.data[ i + 2 ];
|
1344 |
+
data[ i + 3 ] = image.data[ i + 3 ];
|
1345 |
+
|
1346 |
+
}
|
1347 |
+
|
1348 |
+
ctx.putImageData( new ImageData( data, image.width, image.height ), 0, 0 );
|
1349 |
+
|
1350 |
+
} else {
|
1351 |
+
|
1352 |
+
if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
|
1353 |
+
( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
|
1354 |
+
( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ||
|
1355 |
+
( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ) {
|
1356 |
+
|
1357 |
+
ctx.drawImage( image, 0, 0, canvas.width, canvas.height );
|
1358 |
+
|
1359 |
+
} else {
|
1360 |
+
|
1361 |
+
throw new Error( 'THREE.GLTFExporter: Invalid image type. Use HTMLImageElement, HTMLCanvasElement, ImageBitmap or OffscreenCanvas.' );
|
1362 |
+
|
1363 |
+
}
|
1364 |
+
|
1365 |
+
}
|
1366 |
+
|
1367 |
+
if ( options.binary === true ) {
|
1368 |
+
|
1369 |
+
pending.push(
|
1370 |
+
|
1371 |
+
getToBlobPromise( canvas, mimeType )
|
1372 |
+
.then( blob => writer.processBufferViewImage( blob ) )
|
1373 |
+
.then( bufferViewIndex => {
|
1374 |
+
|
1375 |
+
imageDef.bufferView = bufferViewIndex;
|
1376 |
+
|
1377 |
+
} )
|
1378 |
+
|
1379 |
+
);
|
1380 |
+
|
1381 |
+
} else {
|
1382 |
+
|
1383 |
+
if ( canvas.toDataURL !== undefined ) {
|
1384 |
+
|
1385 |
+
imageDef.uri = canvas.toDataURL( mimeType );
|
1386 |
+
|
1387 |
+
} else {
|
1388 |
+
|
1389 |
+
pending.push(
|
1390 |
+
|
1391 |
+
getToBlobPromise( canvas, mimeType )
|
1392 |
+
.then( blob => new FileReader().readAsDataURL( blob ) )
|
1393 |
+
.then( dataURL => {
|
1394 |
+
|
1395 |
+
imageDef.uri = dataURL;
|
1396 |
+
|
1397 |
+
} )
|
1398 |
+
|
1399 |
+
);
|
1400 |
+
|
1401 |
+
}
|
1402 |
+
|
1403 |
+
}
|
1404 |
+
|
1405 |
+
const index = json.images.push( imageDef ) - 1;
|
1406 |
+
cachedImages[ key ] = index;
|
1407 |
+
return index;
|
1408 |
+
|
1409 |
+
} else {
|
1410 |
+
|
1411 |
+
throw new Error( 'THREE.GLTFExporter: No valid image data found. Unable to process texture.' );
|
1412 |
+
|
1413 |
+
}
|
1414 |
+
|
1415 |
+
}
|
1416 |
+
|
1417 |
+
/**
|
1418 |
+
* Process sampler
|
1419 |
+
* @param {Texture} map Texture to process
|
1420 |
+
* @return {Integer} Index of the processed texture in the "samplers" array
|
1421 |
+
*/
|
1422 |
+
processSampler( map ) {
|
1423 |
+
|
1424 |
+
const json = this.json;
|
1425 |
+
|
1426 |
+
if ( ! json.samplers ) json.samplers = [];
|
1427 |
+
|
1428 |
+
const samplerDef = {
|
1429 |
+
magFilter: THREE_TO_WEBGL[ map.magFilter ],
|
1430 |
+
minFilter: THREE_TO_WEBGL[ map.minFilter ],
|
1431 |
+
wrapS: THREE_TO_WEBGL[ map.wrapS ],
|
1432 |
+
wrapT: THREE_TO_WEBGL[ map.wrapT ]
|
1433 |
+
};
|
1434 |
+
|
1435 |
+
return json.samplers.push( samplerDef ) - 1;
|
1436 |
+
|
1437 |
+
}
|
1438 |
+
|
1439 |
+
/**
|
1440 |
+
* Process texture
|
1441 |
+
* @param {Texture} map Map to process
|
1442 |
+
* @return {Integer} Index of the processed texture in the "textures" array
|
1443 |
+
*/
|
1444 |
+
async processTextureAsync( map ) {
|
1445 |
+
|
1446 |
+
const writer = this;
|
1447 |
+
const options = writer.options;
|
1448 |
+
const cache = this.cache;
|
1449 |
+
const json = this.json;
|
1450 |
+
|
1451 |
+
if ( cache.textures.has( map ) ) return cache.textures.get( map );
|
1452 |
+
|
1453 |
+
if ( ! json.textures ) json.textures = [];
|
1454 |
+
|
1455 |
+
// make non-readable textures (e.g. CompressedTexture) readable by blitting them into a new texture
|
1456 |
+
if ( map instanceof CompressedTexture ) {
|
1457 |
+
|
1458 |
+
map = await this.decompressTextureAsync( map, options.maxTextureSize );
|
1459 |
+
|
1460 |
+
}
|
1461 |
+
|
1462 |
+
let mimeType = map.userData.mimeType;
|
1463 |
+
|
1464 |
+
if ( mimeType === 'image/webp' ) mimeType = 'image/png';
|
1465 |
+
|
1466 |
+
const textureDef = {
|
1467 |
+
sampler: this.processSampler( map ),
|
1468 |
+
source: this.processImage( map.image, map.format, map.flipY, mimeType )
|
1469 |
+
};
|
1470 |
+
|
1471 |
+
if ( map.name ) textureDef.name = map.name;
|
1472 |
+
|
1473 |
+
await this._invokeAllAsync( async function ( ext ) {
|
1474 |
+
|
1475 |
+
ext.writeTexture && await ext.writeTexture( map, textureDef );
|
1476 |
+
|
1477 |
+
} );
|
1478 |
+
|
1479 |
+
const index = json.textures.push( textureDef ) - 1;
|
1480 |
+
cache.textures.set( map, index );
|
1481 |
+
return index;
|
1482 |
+
|
1483 |
+
}
|
1484 |
+
|
1485 |
+
/**
|
1486 |
+
* Process material
|
1487 |
+
* @param {THREE.Material} material Material to process
|
1488 |
+
* @return {Integer|null} Index of the processed material in the "materials" array
|
1489 |
+
*/
|
1490 |
+
async processMaterialAsync( material ) {
|
1491 |
+
|
1492 |
+
const cache = this.cache;
|
1493 |
+
const json = this.json;
|
1494 |
+
|
1495 |
+
if ( cache.materials.has( material ) ) return cache.materials.get( material );
|
1496 |
+
|
1497 |
+
if ( material.isShaderMaterial ) {
|
1498 |
+
|
1499 |
+
console.warn( 'GLTFExporter: THREE.ShaderMaterial not supported.' );
|
1500 |
+
return null;
|
1501 |
+
|
1502 |
+
}
|
1503 |
+
|
1504 |
+
if ( ! json.materials ) json.materials = [];
|
1505 |
+
|
1506 |
+
// @QUESTION Should we avoid including any attribute that has the default value?
|
1507 |
+
const materialDef = { pbrMetallicRoughness: {} };
|
1508 |
+
|
1509 |
+
if ( material.isMeshStandardMaterial !== true && material.isMeshBasicMaterial !== true ) {
|
1510 |
+
|
1511 |
+
console.warn( 'GLTFExporter: Use MeshStandardMaterial or MeshBasicMaterial for best results.' );
|
1512 |
+
|
1513 |
+
}
|
1514 |
+
|
1515 |
+
// pbrMetallicRoughness.baseColorFactor
|
1516 |
+
const color = material.color.toArray().concat( [ material.opacity ] );
|
1517 |
+
|
1518 |
+
if ( ! equalArray( color, [ 1, 1, 1, 1 ] ) ) {
|
1519 |
+
|
1520 |
+
materialDef.pbrMetallicRoughness.baseColorFactor = color;
|
1521 |
+
|
1522 |
+
}
|
1523 |
+
|
1524 |
+
if ( material.isMeshStandardMaterial ) {
|
1525 |
+
|
1526 |
+
materialDef.pbrMetallicRoughness.metallicFactor = material.metalness;
|
1527 |
+
materialDef.pbrMetallicRoughness.roughnessFactor = material.roughness;
|
1528 |
+
|
1529 |
+
} else {
|
1530 |
+
|
1531 |
+
materialDef.pbrMetallicRoughness.metallicFactor = 0;
|
1532 |
+
materialDef.pbrMetallicRoughness.roughnessFactor = 1;
|
1533 |
+
|
1534 |
+
}
|
1535 |
+
|
1536 |
+
// pbrMetallicRoughness.metallicRoughnessTexture
|
1537 |
+
if ( material.metalnessMap || material.roughnessMap ) {
|
1538 |
+
|
1539 |
+
const metalRoughTexture = await this.buildMetalRoughTextureAsync( material.metalnessMap, material.roughnessMap );
|
1540 |
+
|
1541 |
+
const metalRoughMapDef = {
|
1542 |
+
index: await this.processTextureAsync( metalRoughTexture ),
|
1543 |
+
texCoord: metalRoughTexture.channel
|
1544 |
+
};
|
1545 |
+
this.applyTextureTransform( metalRoughMapDef, metalRoughTexture );
|
1546 |
+
materialDef.pbrMetallicRoughness.metallicRoughnessTexture = metalRoughMapDef;
|
1547 |
+
|
1548 |
+
}
|
1549 |
+
|
1550 |
+
// pbrMetallicRoughness.baseColorTexture
|
1551 |
+
if ( material.map ) {
|
1552 |
+
|
1553 |
+
const baseColorMapDef = {
|
1554 |
+
index: await this.processTextureAsync( material.map ),
|
1555 |
+
texCoord: material.map.channel
|
1556 |
+
};
|
1557 |
+
this.applyTextureTransform( baseColorMapDef, material.map );
|
1558 |
+
materialDef.pbrMetallicRoughness.baseColorTexture = baseColorMapDef;
|
1559 |
+
|
1560 |
+
}
|
1561 |
+
|
1562 |
+
if ( material.emissive ) {
|
1563 |
+
|
1564 |
+
const emissive = material.emissive;
|
1565 |
+
const maxEmissiveComponent = Math.max( emissive.r, emissive.g, emissive.b );
|
1566 |
+
|
1567 |
+
if ( maxEmissiveComponent > 0 ) {
|
1568 |
+
|
1569 |
+
materialDef.emissiveFactor = material.emissive.toArray();
|
1570 |
+
|
1571 |
+
}
|
1572 |
+
|
1573 |
+
// emissiveTexture
|
1574 |
+
if ( material.emissiveMap ) {
|
1575 |
+
|
1576 |
+
const emissiveMapDef = {
|
1577 |
+
index: await this.processTextureAsync( material.emissiveMap ),
|
1578 |
+
texCoord: material.emissiveMap.channel
|
1579 |
+
};
|
1580 |
+
this.applyTextureTransform( emissiveMapDef, material.emissiveMap );
|
1581 |
+
materialDef.emissiveTexture = emissiveMapDef;
|
1582 |
+
|
1583 |
+
}
|
1584 |
+
|
1585 |
+
}
|
1586 |
+
|
1587 |
+
// normalTexture
|
1588 |
+
if ( material.normalMap ) {
|
1589 |
+
|
1590 |
+
const normalMapDef = {
|
1591 |
+
index: await this.processTextureAsync( material.normalMap ),
|
1592 |
+
texCoord: material.normalMap.channel
|
1593 |
+
};
|
1594 |
+
|
1595 |
+
if ( material.normalScale && material.normalScale.x !== 1 ) {
|
1596 |
+
|
1597 |
+
// glTF normal scale is univariate. Ignore `y`, which may be flipped.
|
1598 |
+
// Context: https://github.com/mrdoob/three.js/issues/11438#issuecomment-507003995
|
1599 |
+
normalMapDef.scale = material.normalScale.x;
|
1600 |
+
|
1601 |
+
}
|
1602 |
+
|
1603 |
+
this.applyTextureTransform( normalMapDef, material.normalMap );
|
1604 |
+
materialDef.normalTexture = normalMapDef;
|
1605 |
+
|
1606 |
+
}
|
1607 |
+
|
1608 |
+
// occlusionTexture
|
1609 |
+
if ( material.aoMap ) {
|
1610 |
+
|
1611 |
+
const occlusionMapDef = {
|
1612 |
+
index: await this.processTextureAsync( material.aoMap ),
|
1613 |
+
texCoord: material.aoMap.channel
|
1614 |
+
};
|
1615 |
+
|
1616 |
+
if ( material.aoMapIntensity !== 1.0 ) {
|
1617 |
+
|
1618 |
+
occlusionMapDef.strength = material.aoMapIntensity;
|
1619 |
+
|
1620 |
+
}
|
1621 |
+
|
1622 |
+
this.applyTextureTransform( occlusionMapDef, material.aoMap );
|
1623 |
+
materialDef.occlusionTexture = occlusionMapDef;
|
1624 |
+
|
1625 |
+
}
|
1626 |
+
|
1627 |
+
// alphaMode
|
1628 |
+
if ( material.transparent ) {
|
1629 |
+
|
1630 |
+
materialDef.alphaMode = 'BLEND';
|
1631 |
+
|
1632 |
+
} else {
|
1633 |
+
|
1634 |
+
if ( material.alphaTest > 0.0 ) {
|
1635 |
+
|
1636 |
+
materialDef.alphaMode = 'MASK';
|
1637 |
+
materialDef.alphaCutoff = material.alphaTest;
|
1638 |
+
|
1639 |
+
}
|
1640 |
+
|
1641 |
+
}
|
1642 |
+
|
1643 |
+
// doubleSided
|
1644 |
+
if ( material.side === DoubleSide ) materialDef.doubleSided = true;
|
1645 |
+
if ( material.name !== '' ) materialDef.name = material.name;
|
1646 |
+
|
1647 |
+
this.serializeUserData( material, materialDef );
|
1648 |
+
|
1649 |
+
await this._invokeAllAsync( async function ( ext ) {
|
1650 |
+
|
1651 |
+
ext.writeMaterialAsync && await ext.writeMaterialAsync( material, materialDef );
|
1652 |
+
|
1653 |
+
} );
|
1654 |
+
|
1655 |
+
const index = json.materials.push( materialDef ) - 1;
|
1656 |
+
cache.materials.set( material, index );
|
1657 |
+
return index;
|
1658 |
+
|
1659 |
+
}
|
1660 |
+
|
1661 |
+
/**
|
1662 |
+
* Process mesh
|
1663 |
+
* @param {THREE.Mesh} mesh Mesh to process
|
1664 |
+
* @return {Integer|null} Index of the processed mesh in the "meshes" array
|
1665 |
+
*/
|
1666 |
+
async processMeshAsync( mesh ) {
|
1667 |
+
|
1668 |
+
const cache = this.cache;
|
1669 |
+
const json = this.json;
|
1670 |
+
|
1671 |
+
const meshCacheKeyParts = [ mesh.geometry.uuid ];
|
1672 |
+
|
1673 |
+
if ( Array.isArray( mesh.material ) ) {
|
1674 |
+
|
1675 |
+
for ( let i = 0, l = mesh.material.length; i < l; i ++ ) {
|
1676 |
+
|
1677 |
+
meshCacheKeyParts.push( mesh.material[ i ].uuid );
|
1678 |
+
|
1679 |
+
}
|
1680 |
+
|
1681 |
+
} else {
|
1682 |
+
|
1683 |
+
meshCacheKeyParts.push( mesh.material.uuid );
|
1684 |
+
|
1685 |
+
}
|
1686 |
+
|
1687 |
+
const meshCacheKey = meshCacheKeyParts.join( ':' );
|
1688 |
+
|
1689 |
+
if ( cache.meshes.has( meshCacheKey ) ) return cache.meshes.get( meshCacheKey );
|
1690 |
+
|
1691 |
+
const geometry = mesh.geometry;
|
1692 |
+
|
1693 |
+
let mode;
|
1694 |
+
|
1695 |
+
// Use the correct mode
|
1696 |
+
if ( mesh.isLineSegments ) {
|
1697 |
+
|
1698 |
+
mode = WEBGL_CONSTANTS.LINES;
|
1699 |
+
|
1700 |
+
} else if ( mesh.isLineLoop ) {
|
1701 |
+
|
1702 |
+
mode = WEBGL_CONSTANTS.LINE_LOOP;
|
1703 |
+
|
1704 |
+
} else if ( mesh.isLine ) {
|
1705 |
+
|
1706 |
+
mode = WEBGL_CONSTANTS.LINE_STRIP;
|
1707 |
+
|
1708 |
+
} else if ( mesh.isPoints ) {
|
1709 |
+
|
1710 |
+
mode = WEBGL_CONSTANTS.POINTS;
|
1711 |
+
|
1712 |
+
} else {
|
1713 |
+
|
1714 |
+
mode = mesh.material.wireframe ? WEBGL_CONSTANTS.LINES : WEBGL_CONSTANTS.TRIANGLES;
|
1715 |
+
|
1716 |
+
}
|
1717 |
+
|
1718 |
+
const meshDef = {};
|
1719 |
+
const attributes = {};
|
1720 |
+
const primitives = [];
|
1721 |
+
const targets = [];
|
1722 |
+
|
1723 |
+
// Conversion between attributes names in threejs and gltf spec
|
1724 |
+
const nameConversion = {
|
1725 |
+
uv: 'TEXCOORD_0',
|
1726 |
+
uv1: 'TEXCOORD_1',
|
1727 |
+
uv2: 'TEXCOORD_2',
|
1728 |
+
uv3: 'TEXCOORD_3',
|
1729 |
+
color: 'COLOR_0',
|
1730 |
+
skinWeight: 'WEIGHTS_0',
|
1731 |
+
skinIndex: 'JOINTS_0'
|
1732 |
+
};
|
1733 |
+
|
1734 |
+
const originalNormal = geometry.getAttribute( 'normal' );
|
1735 |
+
|
1736 |
+
if ( originalNormal !== undefined && ! this.isNormalizedNormalAttribute( originalNormal ) ) {
|
1737 |
+
|
1738 |
+
console.warn( 'THREE.GLTFExporter: Creating normalized normal attribute from the non-normalized one.' );
|
1739 |
+
|
1740 |
+
geometry.setAttribute( 'normal', this.createNormalizedNormalAttribute( originalNormal ) );
|
1741 |
+
|
1742 |
+
}
|
1743 |
+
|
1744 |
+
// @QUESTION Detect if .vertexColors = true?
|
1745 |
+
// For every attribute create an accessor
|
1746 |
+
let modifiedAttribute = null;
|
1747 |
+
|
1748 |
+
for ( let attributeName in geometry.attributes ) {
|
1749 |
+
|
1750 |
+
// Ignore morph target attributes, which are exported later.
|
1751 |
+
if ( attributeName.slice( 0, 5 ) === 'morph' ) continue;
|
1752 |
+
|
1753 |
+
const attribute = geometry.attributes[ attributeName ];
|
1754 |
+
attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
|
1755 |
+
|
1756 |
+
// Prefix all geometry attributes except the ones specifically
|
1757 |
+
// listed in the spec; non-spec attributes are considered custom.
|
1758 |
+
const validVertexAttributes =
|
1759 |
+
/^(POSITION|NORMAL|TANGENT|TEXCOORD_\d+|COLOR_\d+|JOINTS_\d+|WEIGHTS_\d+)$/;
|
1760 |
+
|
1761 |
+
if ( ! validVertexAttributes.test( attributeName ) ) attributeName = '_' + attributeName;
|
1762 |
+
|
1763 |
+
if ( cache.attributes.has( this.getUID( attribute ) ) ) {
|
1764 |
+
|
1765 |
+
attributes[ attributeName ] = cache.attributes.get( this.getUID( attribute ) );
|
1766 |
+
continue;
|
1767 |
+
|
1768 |
+
}
|
1769 |
+
|
1770 |
+
// Enforce glTF vertex attribute requirements:
|
1771 |
+
// - JOINTS_0 must be UNSIGNED_BYTE or UNSIGNED_SHORT
|
1772 |
+
// - Only custom attributes may be INT or UNSIGNED_INT
|
1773 |
+
modifiedAttribute = null;
|
1774 |
+
const array = attribute.array;
|
1775 |
+
|
1776 |
+
if ( attributeName === 'JOINTS_0' &&
|
1777 |
+
! ( array instanceof Uint16Array ) &&
|
1778 |
+
! ( array instanceof Uint8Array ) ) {
|
1779 |
+
|
1780 |
+
console.warn( 'GLTFExporter: Attribute "skinIndex" converted to type UNSIGNED_SHORT.' );
|
1781 |
+
modifiedAttribute = new BufferAttribute( new Uint16Array( array ), attribute.itemSize, attribute.normalized );
|
1782 |
+
|
1783 |
+
} else if ( ( array instanceof Uint32Array || array instanceof Int32Array ) && ! attributeName.startsWith( '_' ) ) {
|
1784 |
+
|
1785 |
+
console.warn( `GLTFExporter: Attribute "${ attributeName }" converted to type FLOAT.` );
|
1786 |
+
modifiedAttribute = GLTFExporter.Utils.toFloat32BufferAttribute( attribute );
|
1787 |
+
|
1788 |
+
}
|
1789 |
+
|
1790 |
+
const accessor = this.processAccessor( modifiedAttribute || attribute, geometry );
|
1791 |
+
|
1792 |
+
if ( accessor !== null ) {
|
1793 |
+
|
1794 |
+
if ( ! attributeName.startsWith( '_' ) ) {
|
1795 |
+
|
1796 |
+
this.detectMeshQuantization( attributeName, attribute );
|
1797 |
+
|
1798 |
+
}
|
1799 |
+
|
1800 |
+
attributes[ attributeName ] = accessor;
|
1801 |
+
cache.attributes.set( this.getUID( attribute ), accessor );
|
1802 |
+
|
1803 |
+
}
|
1804 |
+
|
1805 |
+
}
|
1806 |
+
|
1807 |
+
if ( originalNormal !== undefined ) geometry.setAttribute( 'normal', originalNormal );
|
1808 |
+
|
1809 |
+
// Skip if no exportable attributes found
|
1810 |
+
if ( Object.keys( attributes ).length === 0 ) return null;
|
1811 |
+
|
1812 |
+
// Morph targets
|
1813 |
+
if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {
|
1814 |
+
|
1815 |
+
const weights = [];
|
1816 |
+
const targetNames = [];
|
1817 |
+
const reverseDictionary = {};
|
1818 |
+
|
1819 |
+
if ( mesh.morphTargetDictionary !== undefined ) {
|
1820 |
+
|
1821 |
+
for ( const key in mesh.morphTargetDictionary ) {
|
1822 |
+
|
1823 |
+
reverseDictionary[ mesh.morphTargetDictionary[ key ] ] = key;
|
1824 |
+
|
1825 |
+
}
|
1826 |
+
|
1827 |
+
}
|
1828 |
+
|
1829 |
+
for ( let i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {
|
1830 |
+
|
1831 |
+
const target = {};
|
1832 |
+
let warned = false;
|
1833 |
+
|
1834 |
+
for ( const attributeName in geometry.morphAttributes ) {
|
1835 |
+
|
1836 |
+
// glTF 2.0 morph supports only POSITION/NORMAL/TANGENT.
|
1837 |
+
// Three.js doesn't support TANGENT yet.
|
1838 |
+
|
1839 |
+
if ( attributeName !== 'position' && attributeName !== 'normal' ) {
|
1840 |
+
|
1841 |
+
if ( ! warned ) {
|
1842 |
+
|
1843 |
+
console.warn( 'GLTFExporter: Only POSITION and NORMAL morph are supported.' );
|
1844 |
+
warned = true;
|
1845 |
+
|
1846 |
+
}
|
1847 |
+
|
1848 |
+
continue;
|
1849 |
+
|
1850 |
+
}
|
1851 |
+
|
1852 |
+
const attribute = geometry.morphAttributes[ attributeName ][ i ];
|
1853 |
+
const gltfAttributeName = attributeName.toUpperCase();
|
1854 |
+
|
1855 |
+
// Three.js morph attribute has absolute values while the one of glTF has relative values.
|
1856 |
+
//
|
1857 |
+
// glTF 2.0 Specification:
|
1858 |
+
// https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#morph-targets
|
1859 |
+
|
1860 |
+
const baseAttribute = geometry.attributes[ attributeName ];
|
1861 |
+
|
1862 |
+
if ( cache.attributes.has( this.getUID( attribute, true ) ) ) {
|
1863 |
+
|
1864 |
+
target[ gltfAttributeName ] = cache.attributes.get( this.getUID( attribute, true ) );
|
1865 |
+
continue;
|
1866 |
+
|
1867 |
+
}
|
1868 |
+
|
1869 |
+
// Clones attribute not to override
|
1870 |
+
const relativeAttribute = attribute.clone();
|
1871 |
+
|
1872 |
+
if ( ! geometry.morphTargetsRelative ) {
|
1873 |
+
|
1874 |
+
for ( let j = 0, jl = attribute.count; j < jl; j ++ ) {
|
1875 |
+
|
1876 |
+
for ( let a = 0; a < attribute.itemSize; a ++ ) {
|
1877 |
+
|
1878 |
+
if ( a === 0 ) relativeAttribute.setX( j, attribute.getX( j ) - baseAttribute.getX( j ) );
|
1879 |
+
if ( a === 1 ) relativeAttribute.setY( j, attribute.getY( j ) - baseAttribute.getY( j ) );
|
1880 |
+
if ( a === 2 ) relativeAttribute.setZ( j, attribute.getZ( j ) - baseAttribute.getZ( j ) );
|
1881 |
+
if ( a === 3 ) relativeAttribute.setW( j, attribute.getW( j ) - baseAttribute.getW( j ) );
|
1882 |
+
|
1883 |
+
}
|
1884 |
+
|
1885 |
+
}
|
1886 |
+
|
1887 |
+
}
|
1888 |
+
|
1889 |
+
target[ gltfAttributeName ] = this.processAccessor( relativeAttribute, geometry );
|
1890 |
+
cache.attributes.set( this.getUID( baseAttribute, true ), target[ gltfAttributeName ] );
|
1891 |
+
|
1892 |
+
}
|
1893 |
+
|
1894 |
+
targets.push( target );
|
1895 |
+
|
1896 |
+
weights.push( mesh.morphTargetInfluences[ i ] );
|
1897 |
+
|
1898 |
+
if ( mesh.morphTargetDictionary !== undefined ) targetNames.push( reverseDictionary[ i ] );
|
1899 |
+
|
1900 |
+
}
|
1901 |
+
|
1902 |
+
meshDef.weights = weights;
|
1903 |
+
|
1904 |
+
if ( targetNames.length > 0 ) {
|
1905 |
+
|
1906 |
+
meshDef.extras = {};
|
1907 |
+
meshDef.extras.targetNames = targetNames;
|
1908 |
+
|
1909 |
+
}
|
1910 |
+
|
1911 |
+
}
|
1912 |
+
|
1913 |
+
const isMultiMaterial = Array.isArray( mesh.material );
|
1914 |
+
|
1915 |
+
if ( isMultiMaterial && geometry.groups.length === 0 ) return null;
|
1916 |
+
|
1917 |
+
let didForceIndices = false;
|
1918 |
+
|
1919 |
+
if ( isMultiMaterial && geometry.index === null ) {
|
1920 |
+
|
1921 |
+
const indices = [];
|
1922 |
+
|
1923 |
+
for ( let i = 0, il = geometry.attributes.position.count; i < il; i ++ ) {
|
1924 |
+
|
1925 |
+
indices[ i ] = i;
|
1926 |
+
|
1927 |
+
}
|
1928 |
+
|
1929 |
+
geometry.setIndex( indices );
|
1930 |
+
|
1931 |
+
didForceIndices = true;
|
1932 |
+
|
1933 |
+
}
|
1934 |
+
|
1935 |
+
const materials = isMultiMaterial ? mesh.material : [ mesh.material ];
|
1936 |
+
const groups = isMultiMaterial ? geometry.groups : [ { materialIndex: 0, start: undefined, count: undefined } ];
|
1937 |
+
|
1938 |
+
for ( let i = 0, il = groups.length; i < il; i ++ ) {
|
1939 |
+
|
1940 |
+
const primitive = {
|
1941 |
+
mode: mode,
|
1942 |
+
attributes: attributes,
|
1943 |
+
};
|
1944 |
+
|
1945 |
+
this.serializeUserData( geometry, primitive );
|
1946 |
+
|
1947 |
+
if ( targets.length > 0 ) primitive.targets = targets;
|
1948 |
+
|
1949 |
+
if ( geometry.index !== null ) {
|
1950 |
+
|
1951 |
+
let cacheKey = this.getUID( geometry.index );
|
1952 |
+
|
1953 |
+
if ( groups[ i ].start !== undefined || groups[ i ].count !== undefined ) {
|
1954 |
+
|
1955 |
+
cacheKey += ':' + groups[ i ].start + ':' + groups[ i ].count;
|
1956 |
+
|
1957 |
+
}
|
1958 |
+
|
1959 |
+
if ( cache.attributes.has( cacheKey ) ) {
|
1960 |
+
|
1961 |
+
primitive.indices = cache.attributes.get( cacheKey );
|
1962 |
+
|
1963 |
+
} else {
|
1964 |
+
|
1965 |
+
primitive.indices = this.processAccessor( geometry.index, geometry, groups[ i ].start, groups[ i ].count );
|
1966 |
+
cache.attributes.set( cacheKey, primitive.indices );
|
1967 |
+
|
1968 |
+
}
|
1969 |
+
|
1970 |
+
if ( primitive.indices === null ) delete primitive.indices;
|
1971 |
+
|
1972 |
+
}
|
1973 |
+
|
1974 |
+
const material = await this.processMaterialAsync( materials[ groups[ i ].materialIndex ] );
|
1975 |
+
|
1976 |
+
if ( material !== null ) primitive.material = material;
|
1977 |
+
|
1978 |
+
primitives.push( primitive );
|
1979 |
+
|
1980 |
+
}
|
1981 |
+
|
1982 |
+
if ( didForceIndices === true ) {
|
1983 |
+
|
1984 |
+
geometry.setIndex( null );
|
1985 |
+
|
1986 |
+
}
|
1987 |
+
|
1988 |
+
meshDef.primitives = primitives;
|
1989 |
+
|
1990 |
+
if ( ! json.meshes ) json.meshes = [];
|
1991 |
+
|
1992 |
+
await this._invokeAllAsync( function ( ext ) {
|
1993 |
+
|
1994 |
+
ext.writeMesh && ext.writeMesh( mesh, meshDef );
|
1995 |
+
|
1996 |
+
} );
|
1997 |
+
|
1998 |
+
const index = json.meshes.push( meshDef ) - 1;
|
1999 |
+
cache.meshes.set( meshCacheKey, index );
|
2000 |
+
return index;
|
2001 |
+
|
2002 |
+
}
|
2003 |
+
|
2004 |
+
/**
|
2005 |
+
* If a vertex attribute with a
|
2006 |
+
* [non-standard data type](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#meshes-overview)
|
2007 |
+
* is used, it is checked whether it is a valid data type according to the
|
2008 |
+
* [KHR_mesh_quantization](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_mesh_quantization/README.md)
|
2009 |
+
* extension.
|
2010 |
+
* In this case the extension is automatically added to the list of used extensions.
|
2011 |
+
*
|
2012 |
+
* @param {string} attributeName
|
2013 |
+
* @param {THREE.BufferAttribute} attribute
|
2014 |
+
*/
|
2015 |
+
detectMeshQuantization( attributeName, attribute ) {
|
2016 |
+
|
2017 |
+
if ( this.extensionsUsed[ KHR_MESH_QUANTIZATION ] ) return;
|
2018 |
+
|
2019 |
+
let attrType = undefined;
|
2020 |
+
|
2021 |
+
switch ( attribute.array.constructor ) {
|
2022 |
+
|
2023 |
+
case Int8Array:
|
2024 |
+
|
2025 |
+
attrType = 'byte';
|
2026 |
+
|
2027 |
+
break;
|
2028 |
+
|
2029 |
+
case Uint8Array:
|
2030 |
+
|
2031 |
+
attrType = 'unsigned byte';
|
2032 |
+
|
2033 |
+
break;
|
2034 |
+
|
2035 |
+
case Int16Array:
|
2036 |
+
|
2037 |
+
attrType = 'short';
|
2038 |
+
|
2039 |
+
break;
|
2040 |
+
|
2041 |
+
case Uint16Array:
|
2042 |
+
|
2043 |
+
attrType = 'unsigned short';
|
2044 |
+
|
2045 |
+
break;
|
2046 |
+
|
2047 |
+
default:
|
2048 |
+
|
2049 |
+
return;
|
2050 |
+
|
2051 |
+
}
|
2052 |
+
|
2053 |
+
if ( attribute.normalized ) attrType += ' normalized';
|
2054 |
+
|
2055 |
+
const attrNamePrefix = attributeName.split( '_', 1 )[ 0 ];
|
2056 |
+
|
2057 |
+
if ( KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ] && KHR_mesh_quantization_ExtraAttrTypes[ attrNamePrefix ].includes( attrType ) ) {
|
2058 |
+
|
2059 |
+
this.extensionsUsed[ KHR_MESH_QUANTIZATION ] = true;
|
2060 |
+
this.extensionsRequired[ KHR_MESH_QUANTIZATION ] = true;
|
2061 |
+
|
2062 |
+
}
|
2063 |
+
|
2064 |
+
}
|
2065 |
+
|
2066 |
+
/**
|
2067 |
+
* Process camera
|
2068 |
+
* @param {THREE.Camera} camera Camera to process
|
2069 |
+
* @return {Integer} Index of the processed mesh in the "camera" array
|
2070 |
+
*/
|
2071 |
+
processCamera( camera ) {
|
2072 |
+
|
2073 |
+
const json = this.json;
|
2074 |
+
|
2075 |
+
if ( ! json.cameras ) json.cameras = [];
|
2076 |
+
|
2077 |
+
const isOrtho = camera.isOrthographicCamera;
|
2078 |
+
|
2079 |
+
const cameraDef = {
|
2080 |
+
type: isOrtho ? 'orthographic' : 'perspective'
|
2081 |
+
};
|
2082 |
+
|
2083 |
+
if ( isOrtho ) {
|
2084 |
+
|
2085 |
+
cameraDef.orthographic = {
|
2086 |
+
xmag: camera.right * 2,
|
2087 |
+
ymag: camera.top * 2,
|
2088 |
+
zfar: camera.far <= 0 ? 0.001 : camera.far,
|
2089 |
+
znear: camera.near < 0 ? 0 : camera.near
|
2090 |
+
};
|
2091 |
+
|
2092 |
+
} else {
|
2093 |
+
|
2094 |
+
cameraDef.perspective = {
|
2095 |
+
aspectRatio: camera.aspect,
|
2096 |
+
yfov: MathUtils.degToRad( camera.fov ),
|
2097 |
+
zfar: camera.far <= 0 ? 0.001 : camera.far,
|
2098 |
+
znear: camera.near < 0 ? 0 : camera.near
|
2099 |
+
};
|
2100 |
+
|
2101 |
+
}
|
2102 |
+
|
2103 |
+
// Question: Is saving "type" as name intentional?
|
2104 |
+
if ( camera.name !== '' ) cameraDef.name = camera.type;
|
2105 |
+
|
2106 |
+
return json.cameras.push( cameraDef ) - 1;
|
2107 |
+
|
2108 |
+
}
|
2109 |
+
|
2110 |
+
/**
|
2111 |
+
* Creates glTF animation entry from AnimationClip object.
|
2112 |
+
*
|
2113 |
+
* Status:
|
2114 |
+
* - Only properties listed in PATH_PROPERTIES may be animated.
|
2115 |
+
*
|
2116 |
+
* @param {THREE.AnimationClip} clip
|
2117 |
+
* @param {THREE.Object3D} root
|
2118 |
+
* @return {number|null}
|
2119 |
+
*/
|
2120 |
+
processAnimation( clip, root ) {
|
2121 |
+
|
2122 |
+
const json = this.json;
|
2123 |
+
const nodeMap = this.nodeMap;
|
2124 |
+
|
2125 |
+
if ( ! json.animations ) json.animations = [];
|
2126 |
+
|
2127 |
+
clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip.clone(), root );
|
2128 |
+
|
2129 |
+
const tracks = clip.tracks;
|
2130 |
+
const channels = [];
|
2131 |
+
const samplers = [];
|
2132 |
+
|
2133 |
+
for ( let i = 0; i < tracks.length; ++ i ) {
|
2134 |
+
|
2135 |
+
const track = tracks[ i ];
|
2136 |
+
const trackBinding = PropertyBinding.parseTrackName( track.name );
|
2137 |
+
let trackNode = PropertyBinding.findNode( root, trackBinding.nodeName );
|
2138 |
+
const trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];
|
2139 |
+
|
2140 |
+
if ( trackBinding.objectName === 'bones' ) {
|
2141 |
+
|
2142 |
+
if ( trackNode.isSkinnedMesh === true ) {
|
2143 |
+
|
2144 |
+
trackNode = trackNode.skeleton.getBoneByName( trackBinding.objectIndex );
|
2145 |
+
|
2146 |
+
} else {
|
2147 |
+
|
2148 |
+
trackNode = undefined;
|
2149 |
+
|
2150 |
+
}
|
2151 |
+
|
2152 |
+
}
|
2153 |
+
|
2154 |
+
if ( ! trackNode || ! trackProperty ) {
|
2155 |
+
|
2156 |
+
console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
|
2157 |
+
continue;
|
2158 |
+
|
2159 |
+
}
|
2160 |
+
|
2161 |
+
const inputItemSize = 1;
|
2162 |
+
let outputItemSize = track.values.length / track.times.length;
|
2163 |
+
|
2164 |
+
if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {
|
2165 |
+
|
2166 |
+
outputItemSize /= trackNode.morphTargetInfluences.length;
|
2167 |
+
|
2168 |
+
}
|
2169 |
+
|
2170 |
+
let interpolation;
|
2171 |
+
|
2172 |
+
// @TODO export CubicInterpolant(InterpolateSmooth) as CUBICSPLINE
|
2173 |
+
|
2174 |
+
// Detecting glTF cubic spline interpolant by checking factory method's special property
|
2175 |
+
// GLTFCubicSplineInterpolant is a custom interpolant and track doesn't return
|
2176 |
+
// valid value from .getInterpolation().
|
2177 |
+
if ( track.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline === true ) {
|
2178 |
+
|
2179 |
+
interpolation = 'CUBICSPLINE';
|
2180 |
+
|
2181 |
+
// itemSize of CUBICSPLINE keyframe is 9
|
2182 |
+
// (VEC3 * 3: inTangent, splineVertex, and outTangent)
|
2183 |
+
// but needs to be stored as VEC3 so dividing by 3 here.
|
2184 |
+
outputItemSize /= 3;
|
2185 |
+
|
2186 |
+
} else if ( track.getInterpolation() === InterpolateDiscrete ) {
|
2187 |
+
|
2188 |
+
interpolation = 'STEP';
|
2189 |
+
|
2190 |
+
} else {
|
2191 |
+
|
2192 |
+
interpolation = 'LINEAR';
|
2193 |
+
|
2194 |
+
}
|
2195 |
+
|
2196 |
+
samplers.push( {
|
2197 |
+
input: this.processAccessor( new BufferAttribute( track.times, inputItemSize ) ),
|
2198 |
+
output: this.processAccessor( new BufferAttribute( track.values, outputItemSize ) ),
|
2199 |
+
interpolation: interpolation
|
2200 |
+
} );
|
2201 |
+
|
2202 |
+
channels.push( {
|
2203 |
+
sampler: samplers.length - 1,
|
2204 |
+
target: {
|
2205 |
+
node: nodeMap.get( trackNode ),
|
2206 |
+
path: trackProperty
|
2207 |
+
}
|
2208 |
+
} );
|
2209 |
+
|
2210 |
+
}
|
2211 |
+
|
2212 |
+
json.animations.push( {
|
2213 |
+
name: clip.name || 'clip_' + json.animations.length,
|
2214 |
+
samplers: samplers,
|
2215 |
+
channels: channels
|
2216 |
+
} );
|
2217 |
+
|
2218 |
+
return json.animations.length - 1;
|
2219 |
+
|
2220 |
+
}
|
2221 |
+
|
2222 |
+
/**
|
2223 |
+
* @param {THREE.Object3D} object
|
2224 |
+
* @return {number|null}
|
2225 |
+
*/
|
2226 |
+
processSkin( object ) {
|
2227 |
+
|
2228 |
+
const json = this.json;
|
2229 |
+
const nodeMap = this.nodeMap;
|
2230 |
+
|
2231 |
+
const node = json.nodes[ nodeMap.get( object ) ];
|
2232 |
+
|
2233 |
+
const skeleton = object.skeleton;
|
2234 |
+
|
2235 |
+
if ( skeleton === undefined ) return null;
|
2236 |
+
|
2237 |
+
const rootJoint = object.skeleton.bones[ 0 ];
|
2238 |
+
|
2239 |
+
if ( rootJoint === undefined ) return null;
|
2240 |
+
|
2241 |
+
const joints = [];
|
2242 |
+
const inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 );
|
2243 |
+
const temporaryBoneInverse = new Matrix4();
|
2244 |
+
|
2245 |
+
for ( let i = 0; i < skeleton.bones.length; ++ i ) {
|
2246 |
+
|
2247 |
+
joints.push( nodeMap.get( skeleton.bones[ i ] ) );
|
2248 |
+
temporaryBoneInverse.copy( skeleton.boneInverses[ i ] );
|
2249 |
+
temporaryBoneInverse.multiply( object.bindMatrix ).toArray( inverseBindMatrices, i * 16 );
|
2250 |
+
|
2251 |
+
}
|
2252 |
+
|
2253 |
+
if ( json.skins === undefined ) json.skins = [];
|
2254 |
+
|
2255 |
+
json.skins.push( {
|
2256 |
+
inverseBindMatrices: this.processAccessor( new BufferAttribute( inverseBindMatrices, 16 ) ),
|
2257 |
+
joints: joints,
|
2258 |
+
skeleton: nodeMap.get( rootJoint )
|
2259 |
+
} );
|
2260 |
+
|
2261 |
+
const skinIndex = node.skin = json.skins.length - 1;
|
2262 |
+
|
2263 |
+
return skinIndex;
|
2264 |
+
|
2265 |
+
}
|
2266 |
+
|
2267 |
+
/**
|
2268 |
+
* Process Object3D node
|
2269 |
+
* @param {THREE.Object3D} object Object3D to processNodeAsync
|
2270 |
+
* @return {Integer} Index of the node in the nodes list
|
2271 |
+
*/
|
2272 |
+
async processNodeAsync( object ) {
|
2273 |
+
|
2274 |
+
const json = this.json;
|
2275 |
+
const options = this.options;
|
2276 |
+
const nodeMap = this.nodeMap;
|
2277 |
+
|
2278 |
+
if ( ! json.nodes ) json.nodes = [];
|
2279 |
+
|
2280 |
+
const nodeDef = {};
|
2281 |
+
|
2282 |
+
if ( options.trs ) {
|
2283 |
+
|
2284 |
+
const rotation = object.quaternion.toArray();
|
2285 |
+
const position = object.position.toArray();
|
2286 |
+
const scale = object.scale.toArray();
|
2287 |
+
|
2288 |
+
if ( ! equalArray( rotation, [ 0, 0, 0, 1 ] ) ) {
|
2289 |
+
|
2290 |
+
nodeDef.rotation = rotation;
|
2291 |
+
|
2292 |
+
}
|
2293 |
+
|
2294 |
+
if ( ! equalArray( position, [ 0, 0, 0 ] ) ) {
|
2295 |
+
|
2296 |
+
nodeDef.translation = position;
|
2297 |
+
|
2298 |
+
}
|
2299 |
+
|
2300 |
+
if ( ! equalArray( scale, [ 1, 1, 1 ] ) ) {
|
2301 |
+
|
2302 |
+
nodeDef.scale = scale;
|
2303 |
+
|
2304 |
+
}
|
2305 |
+
|
2306 |
+
} else {
|
2307 |
+
|
2308 |
+
if ( object.matrixAutoUpdate ) {
|
2309 |
+
|
2310 |
+
object.updateMatrix();
|
2311 |
+
|
2312 |
+
}
|
2313 |
+
|
2314 |
+
if ( isIdentityMatrix( object.matrix ) === false ) {
|
2315 |
+
|
2316 |
+
nodeDef.matrix = object.matrix.elements;
|
2317 |
+
|
2318 |
+
}
|
2319 |
+
|
2320 |
+
}
|
2321 |
+
|
2322 |
+
// We don't export empty strings name because it represents no-name in Three.js.
|
2323 |
+
if ( object.name !== '' ) nodeDef.name = String( object.name );
|
2324 |
+
|
2325 |
+
this.serializeUserData( object, nodeDef );
|
2326 |
+
|
2327 |
+
if ( object.isMesh || object.isLine || object.isPoints ) {
|
2328 |
+
|
2329 |
+
const meshIndex = await this.processMeshAsync( object );
|
2330 |
+
|
2331 |
+
if ( meshIndex !== null ) nodeDef.mesh = meshIndex;
|
2332 |
+
|
2333 |
+
} else if ( object.isCamera ) {
|
2334 |
+
|
2335 |
+
nodeDef.camera = this.processCamera( object );
|
2336 |
+
|
2337 |
+
}
|
2338 |
+
|
2339 |
+
if ( object.isSkinnedMesh ) this.skins.push( object );
|
2340 |
+
|
2341 |
+
if ( object.children.length > 0 ) {
|
2342 |
+
|
2343 |
+
const children = [];
|
2344 |
+
|
2345 |
+
for ( let i = 0, l = object.children.length; i < l; i ++ ) {
|
2346 |
+
|
2347 |
+
const child = object.children[ i ];
|
2348 |
+
|
2349 |
+
if ( child.visible || options.onlyVisible === false ) {
|
2350 |
+
|
2351 |
+
const nodeIndex = await this.processNodeAsync( child );
|
2352 |
+
|
2353 |
+
if ( nodeIndex !== null ) children.push( nodeIndex );
|
2354 |
+
|
2355 |
+
}
|
2356 |
+
|
2357 |
+
}
|
2358 |
+
|
2359 |
+
if ( children.length > 0 ) nodeDef.children = children;
|
2360 |
+
|
2361 |
+
}
|
2362 |
+
|
2363 |
+
await this._invokeAllAsync( function ( ext ) {
|
2364 |
+
|
2365 |
+
ext.writeNode && ext.writeNode( object, nodeDef );
|
2366 |
+
|
2367 |
+
} );
|
2368 |
+
|
2369 |
+
const nodeIndex = json.nodes.push( nodeDef ) - 1;
|
2370 |
+
nodeMap.set( object, nodeIndex );
|
2371 |
+
return nodeIndex;
|
2372 |
+
|
2373 |
+
}
|
2374 |
+
|
2375 |
+
/**
|
2376 |
+
* Process Scene
|
2377 |
+
* @param {Scene} scene Scene to process
|
2378 |
+
*/
|
2379 |
+
async processSceneAsync( scene ) {
|
2380 |
+
|
2381 |
+
const json = this.json;
|
2382 |
+
const options = this.options;
|
2383 |
+
|
2384 |
+
if ( ! json.scenes ) {
|
2385 |
+
|
2386 |
+
json.scenes = [];
|
2387 |
+
json.scene = 0;
|
2388 |
+
|
2389 |
+
}
|
2390 |
+
|
2391 |
+
const sceneDef = {};
|
2392 |
+
|
2393 |
+
if ( scene.name !== '' ) sceneDef.name = scene.name;
|
2394 |
+
|
2395 |
+
json.scenes.push( sceneDef );
|
2396 |
+
|
2397 |
+
const nodes = [];
|
2398 |
+
|
2399 |
+
for ( let i = 0, l = scene.children.length; i < l; i ++ ) {
|
2400 |
+
|
2401 |
+
const child = scene.children[ i ];
|
2402 |
+
|
2403 |
+
if ( child.visible || options.onlyVisible === false ) {
|
2404 |
+
|
2405 |
+
const nodeIndex = await this.processNodeAsync( child );
|
2406 |
+
|
2407 |
+
if ( nodeIndex !== null ) nodes.push( nodeIndex );
|
2408 |
+
|
2409 |
+
}
|
2410 |
+
|
2411 |
+
}
|
2412 |
+
|
2413 |
+
if ( nodes.length > 0 ) sceneDef.nodes = nodes;
|
2414 |
+
|
2415 |
+
this.serializeUserData( scene, sceneDef );
|
2416 |
+
|
2417 |
+
}
|
2418 |
+
|
2419 |
+
/**
|
2420 |
+
* Creates a Scene to hold a list of objects and parse it
|
2421 |
+
* @param {Array} objects List of objects to process
|
2422 |
+
*/
|
2423 |
+
async processObjectsAsync( objects ) {
|
2424 |
+
|
2425 |
+
const scene = new Scene();
|
2426 |
+
scene.name = 'AuxScene';
|
2427 |
+
|
2428 |
+
for ( let i = 0; i < objects.length; i ++ ) {
|
2429 |
+
|
2430 |
+
// We push directly to children instead of calling `add` to prevent
|
2431 |
+
// modify the .parent and break its original scene and hierarchy
|
2432 |
+
scene.children.push( objects[ i ] );
|
2433 |
+
|
2434 |
+
}
|
2435 |
+
|
2436 |
+
await this.processSceneAsync( scene );
|
2437 |
+
|
2438 |
+
}
|
2439 |
+
|
2440 |
+
/**
|
2441 |
+
* @param {THREE.Object3D|Array<THREE.Object3D>} input
|
2442 |
+
*/
|
2443 |
+
async processInputAsync( input ) {
|
2444 |
+
|
2445 |
+
const options = this.options;
|
2446 |
+
|
2447 |
+
input = input instanceof Array ? input : [ input ];
|
2448 |
+
|
2449 |
+
await this._invokeAllAsync( function ( ext ) {
|
2450 |
+
|
2451 |
+
ext.beforeParse && ext.beforeParse( input );
|
2452 |
+
|
2453 |
+
} );
|
2454 |
+
|
2455 |
+
const objectsWithoutScene = [];
|
2456 |
+
|
2457 |
+
for ( let i = 0; i < input.length; i ++ ) {
|
2458 |
+
|
2459 |
+
if ( input[ i ] instanceof Scene ) {
|
2460 |
+
|
2461 |
+
await this.processSceneAsync( input[ i ] );
|
2462 |
+
|
2463 |
+
} else {
|
2464 |
+
|
2465 |
+
objectsWithoutScene.push( input[ i ] );
|
2466 |
+
|
2467 |
+
}
|
2468 |
+
|
2469 |
+
}
|
2470 |
+
|
2471 |
+
if ( objectsWithoutScene.length > 0 ) {
|
2472 |
+
|
2473 |
+
await this.processObjectsAsync( objectsWithoutScene );
|
2474 |
+
|
2475 |
+
}
|
2476 |
+
|
2477 |
+
for ( let i = 0; i < this.skins.length; ++ i ) {
|
2478 |
+
|
2479 |
+
this.processSkin( this.skins[ i ] );
|
2480 |
+
|
2481 |
+
}
|
2482 |
+
|
2483 |
+
for ( let i = 0; i < options.animations.length; ++ i ) {
|
2484 |
+
|
2485 |
+
this.processAnimation( options.animations[ i ], input[ 0 ] );
|
2486 |
+
|
2487 |
+
}
|
2488 |
+
|
2489 |
+
await this._invokeAllAsync( function ( ext ) {
|
2490 |
+
|
2491 |
+
ext.afterParse && ext.afterParse( input );
|
2492 |
+
|
2493 |
+
} );
|
2494 |
+
|
2495 |
+
}
|
2496 |
+
|
2497 |
+
async _invokeAllAsync( func ) {
|
2498 |
+
|
2499 |
+
for ( let i = 0, il = this.plugins.length; i < il; i ++ ) {
|
2500 |
+
|
2501 |
+
await func( this.plugins[ i ] );
|
2502 |
+
|
2503 |
+
}
|
2504 |
+
|
2505 |
+
}
|
2506 |
+
|
2507 |
+
}
|
2508 |
+
|
2509 |
+
/**
|
2510 |
+
* Punctual Lights Extension
|
2511 |
+
*
|
2512 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_lights_punctual
|
2513 |
+
*/
|
2514 |
+
class GLTFLightExtension {
|
2515 |
+
|
2516 |
+
constructor( writer ) {
|
2517 |
+
|
2518 |
+
this.writer = writer;
|
2519 |
+
this.name = 'KHR_lights_punctual';
|
2520 |
+
|
2521 |
+
}
|
2522 |
+
|
2523 |
+
writeNode( light, nodeDef ) {
|
2524 |
+
|
2525 |
+
if ( ! light.isLight ) return;
|
2526 |
+
|
2527 |
+
if ( ! light.isDirectionalLight && ! light.isPointLight && ! light.isSpotLight ) {
|
2528 |
+
|
2529 |
+
console.warn( 'THREE.GLTFExporter: Only directional, point, and spot lights are supported.', light );
|
2530 |
+
return;
|
2531 |
+
|
2532 |
+
}
|
2533 |
+
|
2534 |
+
const writer = this.writer;
|
2535 |
+
const json = writer.json;
|
2536 |
+
const extensionsUsed = writer.extensionsUsed;
|
2537 |
+
|
2538 |
+
const lightDef = {};
|
2539 |
+
|
2540 |
+
if ( light.name ) lightDef.name = light.name;
|
2541 |
+
|
2542 |
+
lightDef.color = light.color.toArray();
|
2543 |
+
|
2544 |
+
lightDef.intensity = light.intensity;
|
2545 |
+
|
2546 |
+
if ( light.isDirectionalLight ) {
|
2547 |
+
|
2548 |
+
lightDef.type = 'directional';
|
2549 |
+
|
2550 |
+
} else if ( light.isPointLight ) {
|
2551 |
+
|
2552 |
+
lightDef.type = 'point';
|
2553 |
+
|
2554 |
+
if ( light.distance > 0 ) lightDef.range = light.distance;
|
2555 |
+
|
2556 |
+
} else if ( light.isSpotLight ) {
|
2557 |
+
|
2558 |
+
lightDef.type = 'spot';
|
2559 |
+
|
2560 |
+
if ( light.distance > 0 ) lightDef.range = light.distance;
|
2561 |
+
|
2562 |
+
lightDef.spot = {};
|
2563 |
+
lightDef.spot.innerConeAngle = ( 1.0 - light.penumbra ) * light.angle;
|
2564 |
+
lightDef.spot.outerConeAngle = light.angle;
|
2565 |
+
|
2566 |
+
}
|
2567 |
+
|
2568 |
+
if ( light.decay !== undefined && light.decay !== 2 ) {
|
2569 |
+
|
2570 |
+
console.warn( 'THREE.GLTFExporter: Light decay may be lost. glTF is physically-based, '
|
2571 |
+
+ 'and expects light.decay=2.' );
|
2572 |
+
|
2573 |
+
}
|
2574 |
+
|
2575 |
+
if ( light.target
|
2576 |
+
&& ( light.target.parent !== light
|
2577 |
+
|| light.target.position.x !== 0
|
2578 |
+
|| light.target.position.y !== 0
|
2579 |
+
|| light.target.position.z !== - 1 ) ) {
|
2580 |
+
|
2581 |
+
console.warn( 'THREE.GLTFExporter: Light direction may be lost. For best results, '
|
2582 |
+
+ 'make light.target a child of the light with position 0,0,-1.' );
|
2583 |
+
|
2584 |
+
}
|
2585 |
+
|
2586 |
+
if ( ! extensionsUsed[ this.name ] ) {
|
2587 |
+
|
2588 |
+
json.extensions = json.extensions || {};
|
2589 |
+
json.extensions[ this.name ] = { lights: [] };
|
2590 |
+
extensionsUsed[ this.name ] = true;
|
2591 |
+
|
2592 |
+
}
|
2593 |
+
|
2594 |
+
const lights = json.extensions[ this.name ].lights;
|
2595 |
+
lights.push( lightDef );
|
2596 |
+
|
2597 |
+
nodeDef.extensions = nodeDef.extensions || {};
|
2598 |
+
nodeDef.extensions[ this.name ] = { light: lights.length - 1 };
|
2599 |
+
|
2600 |
+
}
|
2601 |
+
|
2602 |
+
}
|
2603 |
+
|
2604 |
+
/**
|
2605 |
+
* Unlit Materials Extension
|
2606 |
+
*
|
2607 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_unlit
|
2608 |
+
*/
|
2609 |
+
class GLTFMaterialsUnlitExtension {
|
2610 |
+
|
2611 |
+
constructor( writer ) {
|
2612 |
+
|
2613 |
+
this.writer = writer;
|
2614 |
+
this.name = 'KHR_materials_unlit';
|
2615 |
+
|
2616 |
+
}
|
2617 |
+
|
2618 |
+
async writeMaterialAsync( material, materialDef ) {
|
2619 |
+
|
2620 |
+
if ( ! material.isMeshBasicMaterial ) return;
|
2621 |
+
|
2622 |
+
const writer = this.writer;
|
2623 |
+
const extensionsUsed = writer.extensionsUsed;
|
2624 |
+
|
2625 |
+
materialDef.extensions = materialDef.extensions || {};
|
2626 |
+
materialDef.extensions[ this.name ] = {};
|
2627 |
+
|
2628 |
+
extensionsUsed[ this.name ] = true;
|
2629 |
+
|
2630 |
+
materialDef.pbrMetallicRoughness.metallicFactor = 0.0;
|
2631 |
+
materialDef.pbrMetallicRoughness.roughnessFactor = 0.9;
|
2632 |
+
|
2633 |
+
}
|
2634 |
+
|
2635 |
+
}
|
2636 |
+
|
2637 |
+
/**
|
2638 |
+
* Clearcoat Materials Extension
|
2639 |
+
*
|
2640 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_clearcoat
|
2641 |
+
*/
|
2642 |
+
class GLTFMaterialsClearcoatExtension {
|
2643 |
+
|
2644 |
+
constructor( writer ) {
|
2645 |
+
|
2646 |
+
this.writer = writer;
|
2647 |
+
this.name = 'KHR_materials_clearcoat';
|
2648 |
+
|
2649 |
+
}
|
2650 |
+
|
2651 |
+
async writeMaterialAsync( material, materialDef ) {
|
2652 |
+
|
2653 |
+
if ( ! material.isMeshPhysicalMaterial || material.clearcoat === 0 ) return;
|
2654 |
+
|
2655 |
+
const writer = this.writer;
|
2656 |
+
const extensionsUsed = writer.extensionsUsed;
|
2657 |
+
|
2658 |
+
const extensionDef = {};
|
2659 |
+
|
2660 |
+
extensionDef.clearcoatFactor = material.clearcoat;
|
2661 |
+
|
2662 |
+
if ( material.clearcoatMap ) {
|
2663 |
+
|
2664 |
+
const clearcoatMapDef = {
|
2665 |
+
index: await writer.processTextureAsync( material.clearcoatMap ),
|
2666 |
+
texCoord: material.clearcoatMap.channel
|
2667 |
+
};
|
2668 |
+
writer.applyTextureTransform( clearcoatMapDef, material.clearcoatMap );
|
2669 |
+
extensionDef.clearcoatTexture = clearcoatMapDef;
|
2670 |
+
|
2671 |
+
}
|
2672 |
+
|
2673 |
+
extensionDef.clearcoatRoughnessFactor = material.clearcoatRoughness;
|
2674 |
+
|
2675 |
+
if ( material.clearcoatRoughnessMap ) {
|
2676 |
+
|
2677 |
+
const clearcoatRoughnessMapDef = {
|
2678 |
+
index: await writer.processTextureAsync( material.clearcoatRoughnessMap ),
|
2679 |
+
texCoord: material.clearcoatRoughnessMap.channel
|
2680 |
+
};
|
2681 |
+
writer.applyTextureTransform( clearcoatRoughnessMapDef, material.clearcoatRoughnessMap );
|
2682 |
+
extensionDef.clearcoatRoughnessTexture = clearcoatRoughnessMapDef;
|
2683 |
+
|
2684 |
+
}
|
2685 |
+
|
2686 |
+
if ( material.clearcoatNormalMap ) {
|
2687 |
+
|
2688 |
+
const clearcoatNormalMapDef = {
|
2689 |
+
index: await writer.processTextureAsync( material.clearcoatNormalMap ),
|
2690 |
+
texCoord: material.clearcoatNormalMap.channel
|
2691 |
+
};
|
2692 |
+
|
2693 |
+
if ( material.clearcoatNormalScale.x !== 1 ) clearcoatNormalMapDef.scale = material.clearcoatNormalScale.x;
|
2694 |
+
|
2695 |
+
writer.applyTextureTransform( clearcoatNormalMapDef, material.clearcoatNormalMap );
|
2696 |
+
extensionDef.clearcoatNormalTexture = clearcoatNormalMapDef;
|
2697 |
+
|
2698 |
+
}
|
2699 |
+
|
2700 |
+
materialDef.extensions = materialDef.extensions || {};
|
2701 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2702 |
+
|
2703 |
+
extensionsUsed[ this.name ] = true;
|
2704 |
+
|
2705 |
+
|
2706 |
+
}
|
2707 |
+
|
2708 |
+
}
|
2709 |
+
|
2710 |
+
/**
|
2711 |
+
* Materials dispersion Extension
|
2712 |
+
*
|
2713 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_dispersion
|
2714 |
+
*/
|
2715 |
+
class GLTFMaterialsDispersionExtension {
|
2716 |
+
|
2717 |
+
constructor( writer ) {
|
2718 |
+
|
2719 |
+
this.writer = writer;
|
2720 |
+
this.name = 'KHR_materials_dispersion';
|
2721 |
+
|
2722 |
+
}
|
2723 |
+
|
2724 |
+
async writeMaterialAsync( material, materialDef ) {
|
2725 |
+
|
2726 |
+
if ( ! material.isMeshPhysicalMaterial || material.dispersion === 0 ) return;
|
2727 |
+
|
2728 |
+
const writer = this.writer;
|
2729 |
+
const extensionsUsed = writer.extensionsUsed;
|
2730 |
+
|
2731 |
+
const extensionDef = {};
|
2732 |
+
|
2733 |
+
extensionDef.dispersion = material.dispersion;
|
2734 |
+
|
2735 |
+
materialDef.extensions = materialDef.extensions || {};
|
2736 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2737 |
+
|
2738 |
+
extensionsUsed[ this.name ] = true;
|
2739 |
+
|
2740 |
+
}
|
2741 |
+
|
2742 |
+
}
|
2743 |
+
|
2744 |
+
/**
|
2745 |
+
* Iridescence Materials Extension
|
2746 |
+
*
|
2747 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_iridescence
|
2748 |
+
*/
|
2749 |
+
class GLTFMaterialsIridescenceExtension {
|
2750 |
+
|
2751 |
+
constructor( writer ) {
|
2752 |
+
|
2753 |
+
this.writer = writer;
|
2754 |
+
this.name = 'KHR_materials_iridescence';
|
2755 |
+
|
2756 |
+
}
|
2757 |
+
|
2758 |
+
async writeMaterialAsync( material, materialDef ) {
|
2759 |
+
|
2760 |
+
if ( ! material.isMeshPhysicalMaterial || material.iridescence === 0 ) return;
|
2761 |
+
|
2762 |
+
const writer = this.writer;
|
2763 |
+
const extensionsUsed = writer.extensionsUsed;
|
2764 |
+
|
2765 |
+
const extensionDef = {};
|
2766 |
+
|
2767 |
+
extensionDef.iridescenceFactor = material.iridescence;
|
2768 |
+
|
2769 |
+
if ( material.iridescenceMap ) {
|
2770 |
+
|
2771 |
+
const iridescenceMapDef = {
|
2772 |
+
index: await writer.processTextureAsync( material.iridescenceMap ),
|
2773 |
+
texCoord: material.iridescenceMap.channel
|
2774 |
+
};
|
2775 |
+
writer.applyTextureTransform( iridescenceMapDef, material.iridescenceMap );
|
2776 |
+
extensionDef.iridescenceTexture = iridescenceMapDef;
|
2777 |
+
|
2778 |
+
}
|
2779 |
+
|
2780 |
+
extensionDef.iridescenceIor = material.iridescenceIOR;
|
2781 |
+
extensionDef.iridescenceThicknessMinimum = material.iridescenceThicknessRange[ 0 ];
|
2782 |
+
extensionDef.iridescenceThicknessMaximum = material.iridescenceThicknessRange[ 1 ];
|
2783 |
+
|
2784 |
+
if ( material.iridescenceThicknessMap ) {
|
2785 |
+
|
2786 |
+
const iridescenceThicknessMapDef = {
|
2787 |
+
index: await writer.processTextureAsync( material.iridescenceThicknessMap ),
|
2788 |
+
texCoord: material.iridescenceThicknessMap.channel
|
2789 |
+
};
|
2790 |
+
writer.applyTextureTransform( iridescenceThicknessMapDef, material.iridescenceThicknessMap );
|
2791 |
+
extensionDef.iridescenceThicknessTexture = iridescenceThicknessMapDef;
|
2792 |
+
|
2793 |
+
}
|
2794 |
+
|
2795 |
+
materialDef.extensions = materialDef.extensions || {};
|
2796 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2797 |
+
|
2798 |
+
extensionsUsed[ this.name ] = true;
|
2799 |
+
|
2800 |
+
}
|
2801 |
+
|
2802 |
+
}
|
2803 |
+
|
2804 |
+
/**
|
2805 |
+
* Transmission Materials Extension
|
2806 |
+
*
|
2807 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_transmission
|
2808 |
+
*/
|
2809 |
+
class GLTFMaterialsTransmissionExtension {
|
2810 |
+
|
2811 |
+
constructor( writer ) {
|
2812 |
+
|
2813 |
+
this.writer = writer;
|
2814 |
+
this.name = 'KHR_materials_transmission';
|
2815 |
+
|
2816 |
+
}
|
2817 |
+
|
2818 |
+
async writeMaterialAsync( material, materialDef ) {
|
2819 |
+
|
2820 |
+
if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return;
|
2821 |
+
|
2822 |
+
const writer = this.writer;
|
2823 |
+
const extensionsUsed = writer.extensionsUsed;
|
2824 |
+
|
2825 |
+
const extensionDef = {};
|
2826 |
+
|
2827 |
+
extensionDef.transmissionFactor = material.transmission;
|
2828 |
+
|
2829 |
+
if ( material.transmissionMap ) {
|
2830 |
+
|
2831 |
+
const transmissionMapDef = {
|
2832 |
+
index: await writer.processTextureAsync( material.transmissionMap ),
|
2833 |
+
texCoord: material.transmissionMap.channel
|
2834 |
+
};
|
2835 |
+
writer.applyTextureTransform( transmissionMapDef, material.transmissionMap );
|
2836 |
+
extensionDef.transmissionTexture = transmissionMapDef;
|
2837 |
+
|
2838 |
+
}
|
2839 |
+
|
2840 |
+
materialDef.extensions = materialDef.extensions || {};
|
2841 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2842 |
+
|
2843 |
+
extensionsUsed[ this.name ] = true;
|
2844 |
+
|
2845 |
+
}
|
2846 |
+
|
2847 |
+
}
|
2848 |
+
|
2849 |
+
/**
|
2850 |
+
* Materials Volume Extension
|
2851 |
+
*
|
2852 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_volume
|
2853 |
+
*/
|
2854 |
+
class GLTFMaterialsVolumeExtension {
|
2855 |
+
|
2856 |
+
constructor( writer ) {
|
2857 |
+
|
2858 |
+
this.writer = writer;
|
2859 |
+
this.name = 'KHR_materials_volume';
|
2860 |
+
|
2861 |
+
}
|
2862 |
+
|
2863 |
+
async writeMaterialAsync( material, materialDef ) {
|
2864 |
+
|
2865 |
+
if ( ! material.isMeshPhysicalMaterial || material.transmission === 0 ) return;
|
2866 |
+
|
2867 |
+
const writer = this.writer;
|
2868 |
+
const extensionsUsed = writer.extensionsUsed;
|
2869 |
+
|
2870 |
+
const extensionDef = {};
|
2871 |
+
|
2872 |
+
extensionDef.thicknessFactor = material.thickness;
|
2873 |
+
|
2874 |
+
if ( material.thicknessMap ) {
|
2875 |
+
|
2876 |
+
const thicknessMapDef = {
|
2877 |
+
index: await writer.processTextureAsync( material.thicknessMap ),
|
2878 |
+
texCoord: material.thicknessMap.channel
|
2879 |
+
};
|
2880 |
+
writer.applyTextureTransform( thicknessMapDef, material.thicknessMap );
|
2881 |
+
extensionDef.thicknessTexture = thicknessMapDef;
|
2882 |
+
|
2883 |
+
}
|
2884 |
+
|
2885 |
+
if ( material.attenuationDistance !== Infinity ) {
|
2886 |
+
|
2887 |
+
extensionDef.attenuationDistance = material.attenuationDistance;
|
2888 |
+
|
2889 |
+
}
|
2890 |
+
|
2891 |
+
extensionDef.attenuationColor = material.attenuationColor.toArray();
|
2892 |
+
|
2893 |
+
materialDef.extensions = materialDef.extensions || {};
|
2894 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2895 |
+
|
2896 |
+
extensionsUsed[ this.name ] = true;
|
2897 |
+
|
2898 |
+
}
|
2899 |
+
|
2900 |
+
}
|
2901 |
+
|
2902 |
+
/**
|
2903 |
+
* Materials ior Extension
|
2904 |
+
*
|
2905 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_ior
|
2906 |
+
*/
|
2907 |
+
class GLTFMaterialsIorExtension {
|
2908 |
+
|
2909 |
+
constructor( writer ) {
|
2910 |
+
|
2911 |
+
this.writer = writer;
|
2912 |
+
this.name = 'KHR_materials_ior';
|
2913 |
+
|
2914 |
+
}
|
2915 |
+
|
2916 |
+
async writeMaterialAsync( material, materialDef ) {
|
2917 |
+
|
2918 |
+
if ( ! material.isMeshPhysicalMaterial || material.ior === 1.5 ) return;
|
2919 |
+
|
2920 |
+
const writer = this.writer;
|
2921 |
+
const extensionsUsed = writer.extensionsUsed;
|
2922 |
+
|
2923 |
+
const extensionDef = {};
|
2924 |
+
|
2925 |
+
extensionDef.ior = material.ior;
|
2926 |
+
|
2927 |
+
materialDef.extensions = materialDef.extensions || {};
|
2928 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2929 |
+
|
2930 |
+
extensionsUsed[ this.name ] = true;
|
2931 |
+
|
2932 |
+
}
|
2933 |
+
|
2934 |
+
}
|
2935 |
+
|
2936 |
+
/**
|
2937 |
+
* Materials specular Extension
|
2938 |
+
*
|
2939 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_specular
|
2940 |
+
*/
|
2941 |
+
class GLTFMaterialsSpecularExtension {
|
2942 |
+
|
2943 |
+
constructor( writer ) {
|
2944 |
+
|
2945 |
+
this.writer = writer;
|
2946 |
+
this.name = 'KHR_materials_specular';
|
2947 |
+
|
2948 |
+
}
|
2949 |
+
|
2950 |
+
async writeMaterialAsync( material, materialDef ) {
|
2951 |
+
|
2952 |
+
if ( ! material.isMeshPhysicalMaterial || ( material.specularIntensity === 1.0 &&
|
2953 |
+
material.specularColor.equals( DEFAULT_SPECULAR_COLOR ) &&
|
2954 |
+
! material.specularIntensityMap && ! material.specularColorMap ) ) return;
|
2955 |
+
|
2956 |
+
const writer = this.writer;
|
2957 |
+
const extensionsUsed = writer.extensionsUsed;
|
2958 |
+
|
2959 |
+
const extensionDef = {};
|
2960 |
+
|
2961 |
+
if ( material.specularIntensityMap ) {
|
2962 |
+
|
2963 |
+
const specularIntensityMapDef = {
|
2964 |
+
index: await writer.processTextureAsync( material.specularIntensityMap ),
|
2965 |
+
texCoord: material.specularIntensityMap.channel
|
2966 |
+
};
|
2967 |
+
writer.applyTextureTransform( specularIntensityMapDef, material.specularIntensityMap );
|
2968 |
+
extensionDef.specularTexture = specularIntensityMapDef;
|
2969 |
+
|
2970 |
+
}
|
2971 |
+
|
2972 |
+
if ( material.specularColorMap ) {
|
2973 |
+
|
2974 |
+
const specularColorMapDef = {
|
2975 |
+
index: await writer.processTextureAsync( material.specularColorMap ),
|
2976 |
+
texCoord: material.specularColorMap.channel
|
2977 |
+
};
|
2978 |
+
writer.applyTextureTransform( specularColorMapDef, material.specularColorMap );
|
2979 |
+
extensionDef.specularColorTexture = specularColorMapDef;
|
2980 |
+
|
2981 |
+
}
|
2982 |
+
|
2983 |
+
extensionDef.specularFactor = material.specularIntensity;
|
2984 |
+
extensionDef.specularColorFactor = material.specularColor.toArray();
|
2985 |
+
|
2986 |
+
materialDef.extensions = materialDef.extensions || {};
|
2987 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
2988 |
+
|
2989 |
+
extensionsUsed[ this.name ] = true;
|
2990 |
+
|
2991 |
+
}
|
2992 |
+
|
2993 |
+
}
|
2994 |
+
|
2995 |
+
/**
|
2996 |
+
* Sheen Materials Extension
|
2997 |
+
*
|
2998 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_sheen
|
2999 |
+
*/
|
3000 |
+
class GLTFMaterialsSheenExtension {
|
3001 |
+
|
3002 |
+
constructor( writer ) {
|
3003 |
+
|
3004 |
+
this.writer = writer;
|
3005 |
+
this.name = 'KHR_materials_sheen';
|
3006 |
+
|
3007 |
+
}
|
3008 |
+
|
3009 |
+
async writeMaterialAsync( material, materialDef ) {
|
3010 |
+
|
3011 |
+
if ( ! material.isMeshPhysicalMaterial || material.sheen == 0.0 ) return;
|
3012 |
+
|
3013 |
+
const writer = this.writer;
|
3014 |
+
const extensionsUsed = writer.extensionsUsed;
|
3015 |
+
|
3016 |
+
const extensionDef = {};
|
3017 |
+
|
3018 |
+
if ( material.sheenRoughnessMap ) {
|
3019 |
+
|
3020 |
+
const sheenRoughnessMapDef = {
|
3021 |
+
index: await writer.processTextureAsync( material.sheenRoughnessMap ),
|
3022 |
+
texCoord: material.sheenRoughnessMap.channel
|
3023 |
+
};
|
3024 |
+
writer.applyTextureTransform( sheenRoughnessMapDef, material.sheenRoughnessMap );
|
3025 |
+
extensionDef.sheenRoughnessTexture = sheenRoughnessMapDef;
|
3026 |
+
|
3027 |
+
}
|
3028 |
+
|
3029 |
+
if ( material.sheenColorMap ) {
|
3030 |
+
|
3031 |
+
const sheenColorMapDef = {
|
3032 |
+
index: await writer.processTextureAsync( material.sheenColorMap ),
|
3033 |
+
texCoord: material.sheenColorMap.channel
|
3034 |
+
};
|
3035 |
+
writer.applyTextureTransform( sheenColorMapDef, material.sheenColorMap );
|
3036 |
+
extensionDef.sheenColorTexture = sheenColorMapDef;
|
3037 |
+
|
3038 |
+
}
|
3039 |
+
|
3040 |
+
extensionDef.sheenRoughnessFactor = material.sheenRoughness;
|
3041 |
+
extensionDef.sheenColorFactor = material.sheenColor.toArray();
|
3042 |
+
|
3043 |
+
materialDef.extensions = materialDef.extensions || {};
|
3044 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
3045 |
+
|
3046 |
+
extensionsUsed[ this.name ] = true;
|
3047 |
+
|
3048 |
+
}
|
3049 |
+
|
3050 |
+
}
|
3051 |
+
|
3052 |
+
/**
|
3053 |
+
* Anisotropy Materials Extension
|
3054 |
+
*
|
3055 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_anisotropy
|
3056 |
+
*/
|
3057 |
+
class GLTFMaterialsAnisotropyExtension {
|
3058 |
+
|
3059 |
+
constructor( writer ) {
|
3060 |
+
|
3061 |
+
this.writer = writer;
|
3062 |
+
this.name = 'KHR_materials_anisotropy';
|
3063 |
+
|
3064 |
+
}
|
3065 |
+
|
3066 |
+
async writeMaterialAsync( material, materialDef ) {
|
3067 |
+
|
3068 |
+
if ( ! material.isMeshPhysicalMaterial || material.anisotropy == 0.0 ) return;
|
3069 |
+
|
3070 |
+
const writer = this.writer;
|
3071 |
+
const extensionsUsed = writer.extensionsUsed;
|
3072 |
+
|
3073 |
+
const extensionDef = {};
|
3074 |
+
|
3075 |
+
if ( material.anisotropyMap ) {
|
3076 |
+
|
3077 |
+
const anisotropyMapDef = { index: await writer.processTextureAsync( material.anisotropyMap ) };
|
3078 |
+
writer.applyTextureTransform( anisotropyMapDef, material.anisotropyMap );
|
3079 |
+
extensionDef.anisotropyTexture = anisotropyMapDef;
|
3080 |
+
|
3081 |
+
}
|
3082 |
+
|
3083 |
+
extensionDef.anisotropyStrength = material.anisotropy;
|
3084 |
+
extensionDef.anisotropyRotation = material.anisotropyRotation;
|
3085 |
+
|
3086 |
+
materialDef.extensions = materialDef.extensions || {};
|
3087 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
3088 |
+
|
3089 |
+
extensionsUsed[ this.name ] = true;
|
3090 |
+
|
3091 |
+
}
|
3092 |
+
|
3093 |
+
}
|
3094 |
+
|
3095 |
+
/**
|
3096 |
+
* Materials Emissive Strength Extension
|
3097 |
+
*
|
3098 |
+
* Specification: https://github.com/KhronosGroup/glTF/blob/5768b3ce0ef32bc39cdf1bef10b948586635ead3/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md
|
3099 |
+
*/
|
3100 |
+
class GLTFMaterialsEmissiveStrengthExtension {
|
3101 |
+
|
3102 |
+
constructor( writer ) {
|
3103 |
+
|
3104 |
+
this.writer = writer;
|
3105 |
+
this.name = 'KHR_materials_emissive_strength';
|
3106 |
+
|
3107 |
+
}
|
3108 |
+
|
3109 |
+
async writeMaterialAsync( material, materialDef ) {
|
3110 |
+
|
3111 |
+
if ( ! material.isMeshStandardMaterial || material.emissiveIntensity === 1.0 ) return;
|
3112 |
+
|
3113 |
+
const writer = this.writer;
|
3114 |
+
const extensionsUsed = writer.extensionsUsed;
|
3115 |
+
|
3116 |
+
const extensionDef = {};
|
3117 |
+
|
3118 |
+
extensionDef.emissiveStrength = material.emissiveIntensity;
|
3119 |
+
|
3120 |
+
materialDef.extensions = materialDef.extensions || {};
|
3121 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
3122 |
+
|
3123 |
+
extensionsUsed[ this.name ] = true;
|
3124 |
+
|
3125 |
+
}
|
3126 |
+
|
3127 |
+
}
|
3128 |
+
|
3129 |
+
|
3130 |
+
/**
|
3131 |
+
* Materials bump Extension
|
3132 |
+
*
|
3133 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/EXT_materials_bump
|
3134 |
+
*/
|
3135 |
+
class GLTFMaterialsBumpExtension {
|
3136 |
+
|
3137 |
+
constructor( writer ) {
|
3138 |
+
|
3139 |
+
this.writer = writer;
|
3140 |
+
this.name = 'EXT_materials_bump';
|
3141 |
+
|
3142 |
+
}
|
3143 |
+
|
3144 |
+
async writeMaterialAsync( material, materialDef ) {
|
3145 |
+
|
3146 |
+
if ( ! material.isMeshStandardMaterial || (
|
3147 |
+
material.bumpScale === 1 &&
|
3148 |
+
! material.bumpMap ) ) return;
|
3149 |
+
|
3150 |
+
const writer = this.writer;
|
3151 |
+
const extensionsUsed = writer.extensionsUsed;
|
3152 |
+
|
3153 |
+
const extensionDef = {};
|
3154 |
+
|
3155 |
+
if ( material.bumpMap ) {
|
3156 |
+
|
3157 |
+
const bumpMapDef = {
|
3158 |
+
index: await writer.processTextureAsync( material.bumpMap ),
|
3159 |
+
texCoord: material.bumpMap.channel
|
3160 |
+
};
|
3161 |
+
writer.applyTextureTransform( bumpMapDef, material.bumpMap );
|
3162 |
+
extensionDef.bumpTexture = bumpMapDef;
|
3163 |
+
|
3164 |
+
}
|
3165 |
+
|
3166 |
+
extensionDef.bumpFactor = material.bumpScale;
|
3167 |
+
|
3168 |
+
materialDef.extensions = materialDef.extensions || {};
|
3169 |
+
materialDef.extensions[ this.name ] = extensionDef;
|
3170 |
+
|
3171 |
+
extensionsUsed[ this.name ] = true;
|
3172 |
+
|
3173 |
+
}
|
3174 |
+
|
3175 |
+
}
|
3176 |
+
|
3177 |
+
/**
|
3178 |
+
* GPU Instancing Extension
|
3179 |
+
*
|
3180 |
+
* Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing
|
3181 |
+
*/
|
3182 |
+
class GLTFMeshGpuInstancing {
|
3183 |
+
|
3184 |
+
constructor( writer ) {
|
3185 |
+
|
3186 |
+
this.writer = writer;
|
3187 |
+
this.name = 'EXT_mesh_gpu_instancing';
|
3188 |
+
|
3189 |
+
}
|
3190 |
+
|
3191 |
+
writeNode( object, nodeDef ) {
|
3192 |
+
|
3193 |
+
if ( ! object.isInstancedMesh ) return;
|
3194 |
+
|
3195 |
+
const writer = this.writer;
|
3196 |
+
|
3197 |
+
const mesh = object;
|
3198 |
+
|
3199 |
+
const translationAttr = new Float32Array( mesh.count * 3 );
|
3200 |
+
const rotationAttr = new Float32Array( mesh.count * 4 );
|
3201 |
+
const scaleAttr = new Float32Array( mesh.count * 3 );
|
3202 |
+
|
3203 |
+
const matrix = new Matrix4();
|
3204 |
+
const position = new Vector3();
|
3205 |
+
const quaternion = new Quaternion();
|
3206 |
+
const scale = new Vector3();
|
3207 |
+
|
3208 |
+
for ( let i = 0; i < mesh.count; i ++ ) {
|
3209 |
+
|
3210 |
+
mesh.getMatrixAt( i, matrix );
|
3211 |
+
matrix.decompose( position, quaternion, scale );
|
3212 |
+
|
3213 |
+
position.toArray( translationAttr, i * 3 );
|
3214 |
+
quaternion.toArray( rotationAttr, i * 4 );
|
3215 |
+
scale.toArray( scaleAttr, i * 3 );
|
3216 |
+
|
3217 |
+
}
|
3218 |
+
|
3219 |
+
const attributes = {
|
3220 |
+
TRANSLATION: writer.processAccessor( new BufferAttribute( translationAttr, 3 ) ),
|
3221 |
+
ROTATION: writer.processAccessor( new BufferAttribute( rotationAttr, 4 ) ),
|
3222 |
+
SCALE: writer.processAccessor( new BufferAttribute( scaleAttr, 3 ) ),
|
3223 |
+
};
|
3224 |
+
|
3225 |
+
if ( mesh.instanceColor )
|
3226 |
+
attributes._COLOR_0 = writer.processAccessor( mesh.instanceColor );
|
3227 |
+
|
3228 |
+
nodeDef.extensions = nodeDef.extensions || {};
|
3229 |
+
nodeDef.extensions[ this.name ] = { attributes };
|
3230 |
+
|
3231 |
+
writer.extensionsUsed[ this.name ] = true;
|
3232 |
+
writer.extensionsRequired[ this.name ] = true;
|
3233 |
+
|
3234 |
+
}
|
3235 |
+
|
3236 |
+
}
|
3237 |
+
|
3238 |
+
/**
|
3239 |
+
* Static utility functions
|
3240 |
+
*/
|
3241 |
+
GLTFExporter.Utils = {
|
3242 |
+
|
3243 |
+
insertKeyframe: function ( track, time ) {
|
3244 |
+
|
3245 |
+
const tolerance = 0.001; // 1ms
|
3246 |
+
const valueSize = track.getValueSize();
|
3247 |
+
|
3248 |
+
const times = new track.TimeBufferType( track.times.length + 1 );
|
3249 |
+
const values = new track.ValueBufferType( track.values.length + valueSize );
|
3250 |
+
const interpolant = track.createInterpolant( new track.ValueBufferType( valueSize ) );
|
3251 |
+
|
3252 |
+
let index;
|
3253 |
+
|
3254 |
+
if ( track.times.length === 0 ) {
|
3255 |
+
|
3256 |
+
times[ 0 ] = time;
|
3257 |
+
|
3258 |
+
for ( let i = 0; i < valueSize; i ++ ) {
|
3259 |
+
|
3260 |
+
values[ i ] = 0;
|
3261 |
+
|
3262 |
+
}
|
3263 |
+
|
3264 |
+
index = 0;
|
3265 |
+
|
3266 |
+
} else if ( time < track.times[ 0 ] ) {
|
3267 |
+
|
3268 |
+
if ( Math.abs( track.times[ 0 ] - time ) < tolerance ) return 0;
|
3269 |
+
|
3270 |
+
times[ 0 ] = time;
|
3271 |
+
times.set( track.times, 1 );
|
3272 |
+
|
3273 |
+
values.set( interpolant.evaluate( time ), 0 );
|
3274 |
+
values.set( track.values, valueSize );
|
3275 |
+
|
3276 |
+
index = 0;
|
3277 |
+
|
3278 |
+
} else if ( time > track.times[ track.times.length - 1 ] ) {
|
3279 |
+
|
3280 |
+
if ( Math.abs( track.times[ track.times.length - 1 ] - time ) < tolerance ) {
|
3281 |
+
|
3282 |
+
return track.times.length - 1;
|
3283 |
+
|
3284 |
+
}
|
3285 |
+
|
3286 |
+
times[ times.length - 1 ] = time;
|
3287 |
+
times.set( track.times, 0 );
|
3288 |
+
|
3289 |
+
values.set( track.values, 0 );
|
3290 |
+
values.set( interpolant.evaluate( time ), track.values.length );
|
3291 |
+
|
3292 |
+
index = times.length - 1;
|
3293 |
+
|
3294 |
+
} else {
|
3295 |
+
|
3296 |
+
for ( let i = 0; i < track.times.length; i ++ ) {
|
3297 |
+
|
3298 |
+
if ( Math.abs( track.times[ i ] - time ) < tolerance ) return i;
|
3299 |
+
|
3300 |
+
if ( track.times[ i ] < time && track.times[ i + 1 ] > time ) {
|
3301 |
+
|
3302 |
+
times.set( track.times.slice( 0, i + 1 ), 0 );
|
3303 |
+
times[ i + 1 ] = time;
|
3304 |
+
times.set( track.times.slice( i + 1 ), i + 2 );
|
3305 |
+
|
3306 |
+
values.set( track.values.slice( 0, ( i + 1 ) * valueSize ), 0 );
|
3307 |
+
values.set( interpolant.evaluate( time ), ( i + 1 ) * valueSize );
|
3308 |
+
values.set( track.values.slice( ( i + 1 ) * valueSize ), ( i + 2 ) * valueSize );
|
3309 |
+
|
3310 |
+
index = i + 1;
|
3311 |
+
|
3312 |
+
break;
|
3313 |
+
|
3314 |
+
}
|
3315 |
+
|
3316 |
+
}
|
3317 |
+
|
3318 |
+
}
|
3319 |
+
|
3320 |
+
track.times = times;
|
3321 |
+
track.values = values;
|
3322 |
+
|
3323 |
+
return index;
|
3324 |
+
|
3325 |
+
},
|
3326 |
+
|
3327 |
+
mergeMorphTargetTracks: function ( clip, root ) {
|
3328 |
+
|
3329 |
+
const tracks = [];
|
3330 |
+
const mergedTracks = {};
|
3331 |
+
const sourceTracks = clip.tracks;
|
3332 |
+
|
3333 |
+
for ( let i = 0; i < sourceTracks.length; ++ i ) {
|
3334 |
+
|
3335 |
+
let sourceTrack = sourceTracks[ i ];
|
3336 |
+
const sourceTrackBinding = PropertyBinding.parseTrackName( sourceTrack.name );
|
3337 |
+
const sourceTrackNode = PropertyBinding.findNode( root, sourceTrackBinding.nodeName );
|
3338 |
+
|
3339 |
+
if ( sourceTrackBinding.propertyName !== 'morphTargetInfluences' || sourceTrackBinding.propertyIndex === undefined ) {
|
3340 |
+
|
3341 |
+
// Tracks that don't affect morph targets, or that affect all morph targets together, can be left as-is.
|
3342 |
+
tracks.push( sourceTrack );
|
3343 |
+
continue;
|
3344 |
+
|
3345 |
+
}
|
3346 |
+
|
3347 |
+
if ( sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodDiscrete
|
3348 |
+
&& sourceTrack.createInterpolant !== sourceTrack.InterpolantFactoryMethodLinear ) {
|
3349 |
+
|
3350 |
+
if ( sourceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
|
3351 |
+
|
3352 |
+
// This should never happen, because glTF morph target animations
|
3353 |
+
// affect all targets already.
|
3354 |
+
throw new Error( 'THREE.GLTFExporter: Cannot merge tracks with glTF CUBICSPLINE interpolation.' );
|
3355 |
+
|
3356 |
+
}
|
3357 |
+
|
3358 |
+
console.warn( 'THREE.GLTFExporter: Morph target interpolation mode not yet supported. Using LINEAR instead.' );
|
3359 |
+
|
3360 |
+
sourceTrack = sourceTrack.clone();
|
3361 |
+
sourceTrack.setInterpolation( InterpolateLinear );
|
3362 |
+
|
3363 |
+
}
|
3364 |
+
|
3365 |
+
const targetCount = sourceTrackNode.morphTargetInfluences.length;
|
3366 |
+
const targetIndex = sourceTrackNode.morphTargetDictionary[ sourceTrackBinding.propertyIndex ];
|
3367 |
+
|
3368 |
+
if ( targetIndex === undefined ) {
|
3369 |
+
|
3370 |
+
throw new Error( 'THREE.GLTFExporter: Morph target name not found: ' + sourceTrackBinding.propertyIndex );
|
3371 |
+
|
3372 |
+
}
|
3373 |
+
|
3374 |
+
let mergedTrack;
|
3375 |
+
|
3376 |
+
// If this is the first time we've seen this object, create a new
|
3377 |
+
// track to store merged keyframe data for each morph target.
|
3378 |
+
if ( mergedTracks[ sourceTrackNode.uuid ] === undefined ) {
|
3379 |
+
|
3380 |
+
mergedTrack = sourceTrack.clone();
|
3381 |
+
|
3382 |
+
const values = new mergedTrack.ValueBufferType( targetCount * mergedTrack.times.length );
|
3383 |
+
|
3384 |
+
for ( let j = 0; j < mergedTrack.times.length; j ++ ) {
|
3385 |
+
|
3386 |
+
values[ j * targetCount + targetIndex ] = mergedTrack.values[ j ];
|
3387 |
+
|
3388 |
+
}
|
3389 |
+
|
3390 |
+
// We need to take into consideration the intended target node
|
3391 |
+
// of our original un-merged morphTarget animation.
|
3392 |
+
mergedTrack.name = ( sourceTrackBinding.nodeName || '' ) + '.morphTargetInfluences';
|
3393 |
+
mergedTrack.values = values;
|
3394 |
+
|
3395 |
+
mergedTracks[ sourceTrackNode.uuid ] = mergedTrack;
|
3396 |
+
tracks.push( mergedTrack );
|
3397 |
+
|
3398 |
+
continue;
|
3399 |
+
|
3400 |
+
}
|
3401 |
+
|
3402 |
+
const sourceInterpolant = sourceTrack.createInterpolant( new sourceTrack.ValueBufferType( 1 ) );
|
3403 |
+
|
3404 |
+
mergedTrack = mergedTracks[ sourceTrackNode.uuid ];
|
3405 |
+
|
3406 |
+
// For every existing keyframe of the merged track, write a (possibly
|
3407 |
+
// interpolated) value from the source track.
|
3408 |
+
for ( let j = 0; j < mergedTrack.times.length; j ++ ) {
|
3409 |
+
|
3410 |
+
mergedTrack.values[ j * targetCount + targetIndex ] = sourceInterpolant.evaluate( mergedTrack.times[ j ] );
|
3411 |
+
|
3412 |
+
}
|
3413 |
+
|
3414 |
+
// For every existing keyframe of the source track, write a (possibly
|
3415 |
+
// new) keyframe to the merged track. Values from the previous loop may
|
3416 |
+
// be written again, but keyframes are de-duplicated.
|
3417 |
+
for ( let j = 0; j < sourceTrack.times.length; j ++ ) {
|
3418 |
+
|
3419 |
+
const keyframeIndex = this.insertKeyframe( mergedTrack, sourceTrack.times[ j ] );
|
3420 |
+
mergedTrack.values[ keyframeIndex * targetCount + targetIndex ] = sourceTrack.values[ j ];
|
3421 |
+
|
3422 |
+
}
|
3423 |
+
|
3424 |
+
}
|
3425 |
+
|
3426 |
+
clip.tracks = tracks;
|
3427 |
+
|
3428 |
+
return clip;
|
3429 |
+
|
3430 |
+
},
|
3431 |
+
|
3432 |
+
toFloat32BufferAttribute: function ( srcAttribute ) {
|
3433 |
+
|
3434 |
+
const dstAttribute = new BufferAttribute( new Float32Array( srcAttribute.count * srcAttribute.itemSize ), srcAttribute.itemSize, false );
|
3435 |
+
|
3436 |
+
if ( ! srcAttribute.normalized && ! srcAttribute.isInterleavedBufferAttribute ) {
|
3437 |
+
|
3438 |
+
dstAttribute.array.set( srcAttribute.array );
|
3439 |
+
|
3440 |
+
return dstAttribute;
|
3441 |
+
|
3442 |
+
}
|
3443 |
+
|
3444 |
+
for ( let i = 0, il = srcAttribute.count; i < il; i ++ ) {
|
3445 |
+
|
3446 |
+
for ( let j = 0; j < srcAttribute.itemSize; j ++ ) {
|
3447 |
+
|
3448 |
+
dstAttribute.setComponent( i, j, srcAttribute.getComponent( i, j ) );
|
3449 |
+
|
3450 |
+
}
|
3451 |
+
|
3452 |
+
}
|
3453 |
+
|
3454 |
+
return dstAttribute;
|
3455 |
+
|
3456 |
+
}
|
3457 |
+
|
3458 |
+
};
|
3459 |
+
|
3460 |
+
export { GLTFExporter };
|
libs/three.js/0.172.0/jsm/exporters/KTX2Exporter.js
ADDED
@@ -0,0 +1,323 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
ColorManagement,
|
3 |
+
FloatType,
|
4 |
+
HalfFloatType,
|
5 |
+
UnsignedByteType,
|
6 |
+
RGBAFormat,
|
7 |
+
RGFormat,
|
8 |
+
RGIntegerFormat,
|
9 |
+
RedFormat,
|
10 |
+
RedIntegerFormat,
|
11 |
+
NoColorSpace,
|
12 |
+
LinearSRGBColorSpace,
|
13 |
+
SRGBColorSpace,
|
14 |
+
SRGBTransfer,
|
15 |
+
DataTexture,
|
16 |
+
REVISION,
|
17 |
+
} from 'three';
|
18 |
+
|
19 |
+
import {
|
20 |
+
write,
|
21 |
+
KTX2Container,
|
22 |
+
KHR_DF_CHANNEL_RGBSDA_ALPHA,
|
23 |
+
KHR_DF_CHANNEL_RGBSDA_BLUE,
|
24 |
+
KHR_DF_CHANNEL_RGBSDA_GREEN,
|
25 |
+
KHR_DF_CHANNEL_RGBSDA_RED,
|
26 |
+
KHR_DF_MODEL_RGBSDA,
|
27 |
+
KHR_DF_PRIMARIES_BT709,
|
28 |
+
KHR_DF_PRIMARIES_UNSPECIFIED,
|
29 |
+
KHR_DF_SAMPLE_DATATYPE_FLOAT,
|
30 |
+
KHR_DF_SAMPLE_DATATYPE_LINEAR,
|
31 |
+
KHR_DF_SAMPLE_DATATYPE_SIGNED,
|
32 |
+
KHR_DF_TRANSFER_LINEAR,
|
33 |
+
KHR_DF_TRANSFER_SRGB,
|
34 |
+
VK_FORMAT_R16_SFLOAT,
|
35 |
+
VK_FORMAT_R16G16_SFLOAT,
|
36 |
+
VK_FORMAT_R16G16B16A16_SFLOAT,
|
37 |
+
VK_FORMAT_R32_SFLOAT,
|
38 |
+
VK_FORMAT_R32G32_SFLOAT,
|
39 |
+
VK_FORMAT_R32G32B32A32_SFLOAT,
|
40 |
+
VK_FORMAT_R8_SRGB,
|
41 |
+
VK_FORMAT_R8_UNORM,
|
42 |
+
VK_FORMAT_R8G8_SRGB,
|
43 |
+
VK_FORMAT_R8G8_UNORM,
|
44 |
+
VK_FORMAT_R8G8B8A8_SRGB,
|
45 |
+
VK_FORMAT_R8G8B8A8_UNORM,
|
46 |
+
} from '../libs/ktx-parse.module.js';
|
47 |
+
|
48 |
+
/**
|
49 |
+
* References:
|
50 |
+
* - https://github.khronos.org/KTX-Specification/ktxspec.v2.html
|
51 |
+
* - https://registry.khronos.org/DataFormat/specs/1.3/dataformat.1.3.html
|
52 |
+
* - https://github.com/donmccurdy/KTX-Parse
|
53 |
+
*/
|
54 |
+
|
55 |
+
const VK_FORMAT_MAP = {
|
56 |
+
|
57 |
+
[ RGBAFormat ]: {
|
58 |
+
[ FloatType ]: {
|
59 |
+
[ NoColorSpace ]: VK_FORMAT_R32G32B32A32_SFLOAT,
|
60 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R32G32B32A32_SFLOAT,
|
61 |
+
},
|
62 |
+
[ HalfFloatType ]: {
|
63 |
+
[ NoColorSpace ]: VK_FORMAT_R16G16B16A16_SFLOAT,
|
64 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R16G16B16A16_SFLOAT,
|
65 |
+
},
|
66 |
+
[ UnsignedByteType ]: {
|
67 |
+
[ NoColorSpace ]: VK_FORMAT_R8G8B8A8_UNORM,
|
68 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R8G8B8A8_UNORM,
|
69 |
+
[ SRGBColorSpace ]: VK_FORMAT_R8G8B8A8_SRGB,
|
70 |
+
},
|
71 |
+
},
|
72 |
+
|
73 |
+
[ RGFormat ]: {
|
74 |
+
[ FloatType ]: {
|
75 |
+
[ NoColorSpace ]: VK_FORMAT_R32G32_SFLOAT,
|
76 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R32G32_SFLOAT,
|
77 |
+
},
|
78 |
+
[ HalfFloatType ]: {
|
79 |
+
[ NoColorSpace ]: VK_FORMAT_R16G16_SFLOAT,
|
80 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R16G16_SFLOAT,
|
81 |
+
},
|
82 |
+
[ UnsignedByteType ]: {
|
83 |
+
[ NoColorSpace ]: VK_FORMAT_R8G8_UNORM,
|
84 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R8G8_UNORM,
|
85 |
+
[ SRGBColorSpace ]: VK_FORMAT_R8G8_SRGB,
|
86 |
+
},
|
87 |
+
},
|
88 |
+
|
89 |
+
[ RedFormat ]: {
|
90 |
+
[ FloatType ]: {
|
91 |
+
[ NoColorSpace ]: VK_FORMAT_R32_SFLOAT,
|
92 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R32_SFLOAT,
|
93 |
+
},
|
94 |
+
[ HalfFloatType ]: {
|
95 |
+
[ NoColorSpace ]: VK_FORMAT_R16_SFLOAT,
|
96 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R16_SFLOAT,
|
97 |
+
},
|
98 |
+
[ UnsignedByteType ]: {
|
99 |
+
[ NoColorSpace ]: VK_FORMAT_R8_UNORM,
|
100 |
+
[ LinearSRGBColorSpace ]: VK_FORMAT_R8_UNORM,
|
101 |
+
[ SRGBColorSpace ]: VK_FORMAT_R8_SRGB,
|
102 |
+
},
|
103 |
+
},
|
104 |
+
|
105 |
+
};
|
106 |
+
|
107 |
+
const KHR_DF_CHANNEL_MAP = [
|
108 |
+
|
109 |
+
KHR_DF_CHANNEL_RGBSDA_RED,
|
110 |
+
KHR_DF_CHANNEL_RGBSDA_GREEN,
|
111 |
+
KHR_DF_CHANNEL_RGBSDA_BLUE,
|
112 |
+
KHR_DF_CHANNEL_RGBSDA_ALPHA,
|
113 |
+
|
114 |
+
];
|
115 |
+
|
116 |
+
// TODO: sampleLower and sampleUpper may change based on color space.
|
117 |
+
const KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER = {
|
118 |
+
|
119 |
+
[ FloatType ]: [ 0xbf800000, 0x3f800000 ],
|
120 |
+
[ HalfFloatType ]: [ 0xbf800000, 0x3f800000 ],
|
121 |
+
[ UnsignedByteType ]: [ 0, 255 ],
|
122 |
+
|
123 |
+
};
|
124 |
+
|
125 |
+
const ERROR_INPUT = 'THREE.KTX2Exporter: Supported inputs are DataTexture, Data3DTexture, or WebGLRenderer and WebGLRenderTarget.';
|
126 |
+
const ERROR_FORMAT = 'THREE.KTX2Exporter: Supported formats are RGBAFormat, RGFormat, or RedFormat.';
|
127 |
+
const ERROR_TYPE = 'THREE.KTX2Exporter: Supported types are FloatType, HalfFloatType, or UnsignedByteType."';
|
128 |
+
const ERROR_COLOR_SPACE = 'THREE.KTX2Exporter: Supported color spaces are SRGBColorSpace (UnsignedByteType only), LinearSRGBColorSpace, or NoColorSpace.';
|
129 |
+
|
130 |
+
export class KTX2Exporter {
|
131 |
+
|
132 |
+
async parse( arg1, arg2 ) {
|
133 |
+
|
134 |
+
let texture;
|
135 |
+
|
136 |
+
if ( arg1.isDataTexture || arg1.isData3DTexture ) {
|
137 |
+
|
138 |
+
texture = arg1;
|
139 |
+
|
140 |
+
} else if ( ( arg1.isWebGLRenderer || arg1.isWebGPURenderer ) && arg2.isRenderTarget ) {
|
141 |
+
|
142 |
+
texture = await toDataTexture( arg1, arg2 );
|
143 |
+
|
144 |
+
} else {
|
145 |
+
|
146 |
+
throw new Error( ERROR_INPUT );
|
147 |
+
|
148 |
+
}
|
149 |
+
|
150 |
+
if ( VK_FORMAT_MAP[ texture.format ] === undefined ) {
|
151 |
+
|
152 |
+
throw new Error( ERROR_FORMAT );
|
153 |
+
|
154 |
+
}
|
155 |
+
|
156 |
+
if ( VK_FORMAT_MAP[ texture.format ][ texture.type ] === undefined ) {
|
157 |
+
|
158 |
+
throw new Error( ERROR_TYPE );
|
159 |
+
|
160 |
+
}
|
161 |
+
|
162 |
+
if ( VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.colorSpace ] === undefined ) {
|
163 |
+
|
164 |
+
throw new Error( ERROR_COLOR_SPACE );
|
165 |
+
|
166 |
+
}
|
167 |
+
|
168 |
+
//
|
169 |
+
|
170 |
+
const array = texture.image.data;
|
171 |
+
const channelCount = getChannelCount( texture );
|
172 |
+
const container = new KTX2Container();
|
173 |
+
|
174 |
+
container.vkFormat = VK_FORMAT_MAP[ texture.format ][ texture.type ][ texture.colorSpace ];
|
175 |
+
container.typeSize = array.BYTES_PER_ELEMENT;
|
176 |
+
container.pixelWidth = texture.image.width;
|
177 |
+
container.pixelHeight = texture.image.height;
|
178 |
+
|
179 |
+
if ( texture.isData3DTexture ) {
|
180 |
+
|
181 |
+
container.pixelDepth = texture.image.depth;
|
182 |
+
|
183 |
+
}
|
184 |
+
|
185 |
+
//
|
186 |
+
|
187 |
+
const basicDesc = container.dataFormatDescriptor[ 0 ];
|
188 |
+
|
189 |
+
basicDesc.colorModel = KHR_DF_MODEL_RGBSDA;
|
190 |
+
basicDesc.colorPrimaries = texture.colorSpace === NoColorSpace
|
191 |
+
? KHR_DF_PRIMARIES_UNSPECIFIED
|
192 |
+
: KHR_DF_PRIMARIES_BT709;
|
193 |
+
basicDesc.transferFunction = ColorManagement.getTransfer( texture.colorSpace ) === SRGBTransfer
|
194 |
+
? KHR_DF_TRANSFER_SRGB
|
195 |
+
: KHR_DF_TRANSFER_LINEAR;
|
196 |
+
|
197 |
+
basicDesc.texelBlockDimension = [ 0, 0, 0, 0 ];
|
198 |
+
|
199 |
+
basicDesc.bytesPlane = [
|
200 |
+
|
201 |
+
container.typeSize * channelCount, 0, 0, 0, 0, 0, 0, 0,
|
202 |
+
|
203 |
+
];
|
204 |
+
|
205 |
+
for ( let i = 0; i < channelCount; ++ i ) {
|
206 |
+
|
207 |
+
let channelType = KHR_DF_CHANNEL_MAP[ i ];
|
208 |
+
|
209 |
+
// Assign KHR_DF_SAMPLE_DATATYPE_LINEAR if the channel is linear _and_ differs from the transfer function.
|
210 |
+
if ( channelType === KHR_DF_CHANNEL_RGBSDA_ALPHA && basicDesc.transferFunction !== KHR_DF_TRANSFER_LINEAR ) {
|
211 |
+
|
212 |
+
channelType |= KHR_DF_SAMPLE_DATATYPE_LINEAR;
|
213 |
+
|
214 |
+
}
|
215 |
+
|
216 |
+
if ( texture.type === FloatType || texture.type === HalfFloatType ) {
|
217 |
+
|
218 |
+
channelType |= KHR_DF_SAMPLE_DATATYPE_FLOAT;
|
219 |
+
channelType |= KHR_DF_SAMPLE_DATATYPE_SIGNED;
|
220 |
+
|
221 |
+
}
|
222 |
+
|
223 |
+
basicDesc.samples.push( {
|
224 |
+
|
225 |
+
channelType: channelType,
|
226 |
+
bitOffset: i * array.BYTES_PER_ELEMENT * 8,
|
227 |
+
bitLength: array.BYTES_PER_ELEMENT * 8 - 1,
|
228 |
+
samplePosition: [ 0, 0, 0, 0 ],
|
229 |
+
sampleLower: KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER[ texture.type ][ 0 ],
|
230 |
+
sampleUpper: KHR_DF_CHANNEL_SAMPLE_LOWER_UPPER[ texture.type ][ 1 ],
|
231 |
+
|
232 |
+
} );
|
233 |
+
|
234 |
+
}
|
235 |
+
|
236 |
+
//
|
237 |
+
|
238 |
+
container.levels = [ {
|
239 |
+
|
240 |
+
levelData: new Uint8Array( array.buffer, array.byteOffset, array.byteLength ),
|
241 |
+
uncompressedByteLength: array.byteLength,
|
242 |
+
|
243 |
+
} ];
|
244 |
+
|
245 |
+
//
|
246 |
+
|
247 |
+
container.keyValue[ 'KTXwriter' ] = `three.js ${ REVISION }`;
|
248 |
+
|
249 |
+
//
|
250 |
+
|
251 |
+
return write( container, { keepWriter: true } );
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
}
|
256 |
+
|
257 |
+
async function toDataTexture( renderer, rtt ) {
|
258 |
+
|
259 |
+
const channelCount = getChannelCount( rtt.texture );
|
260 |
+
|
261 |
+
let view;
|
262 |
+
|
263 |
+
if ( renderer.isWebGLRenderer ) {
|
264 |
+
|
265 |
+
if ( rtt.texture.type === FloatType ) {
|
266 |
+
|
267 |
+
view = new Float32Array( rtt.width * rtt.height * channelCount );
|
268 |
+
|
269 |
+
} else if ( rtt.texture.type === HalfFloatType ) {
|
270 |
+
|
271 |
+
view = new Uint16Array( rtt.width * rtt.height * channelCount );
|
272 |
+
|
273 |
+
} else if ( rtt.texture.type === UnsignedByteType ) {
|
274 |
+
|
275 |
+
view = new Uint8Array( rtt.width * rtt.height * channelCount );
|
276 |
+
|
277 |
+
} else {
|
278 |
+
|
279 |
+
throw new Error( ERROR_TYPE );
|
280 |
+
|
281 |
+
}
|
282 |
+
|
283 |
+
await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, rtt.width, rtt.height, view );
|
284 |
+
|
285 |
+
} else {
|
286 |
+
|
287 |
+
view = await renderer.readRenderTargetPixelsAsync( rtt, 0, 0, rtt.width, rtt.height );
|
288 |
+
|
289 |
+
}
|
290 |
+
|
291 |
+
const texture = new DataTexture( view, rtt.width, rtt.height, rtt.texture.format, rtt.texture.type );
|
292 |
+
|
293 |
+
texture.colorSpace = rtt.texture.colorSpace;
|
294 |
+
|
295 |
+
return texture;
|
296 |
+
|
297 |
+
}
|
298 |
+
|
299 |
+
function getChannelCount( texture ) {
|
300 |
+
|
301 |
+
switch ( texture.format ) {
|
302 |
+
|
303 |
+
case RGBAFormat:
|
304 |
+
|
305 |
+
return 4;
|
306 |
+
|
307 |
+
case RGFormat:
|
308 |
+
case RGIntegerFormat:
|
309 |
+
|
310 |
+
return 2;
|
311 |
+
|
312 |
+
case RedFormat:
|
313 |
+
case RedIntegerFormat:
|
314 |
+
|
315 |
+
return 1;
|
316 |
+
|
317 |
+
default:
|
318 |
+
|
319 |
+
throw new Error( ERROR_FORMAT );
|
320 |
+
|
321 |
+
}
|
322 |
+
|
323 |
+
}
|
libs/three.js/0.172.0/jsm/exporters/OBJExporter.js
ADDED
@@ -0,0 +1,288 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Color,
|
3 |
+
ColorManagement,
|
4 |
+
Matrix3,
|
5 |
+
SRGBColorSpace,
|
6 |
+
Vector2,
|
7 |
+
Vector3
|
8 |
+
} from 'three';
|
9 |
+
|
10 |
+
class OBJExporter {
|
11 |
+
|
12 |
+
parse( object ) {
|
13 |
+
|
14 |
+
let output = '';
|
15 |
+
|
16 |
+
let indexVertex = 0;
|
17 |
+
let indexVertexUvs = 0;
|
18 |
+
let indexNormals = 0;
|
19 |
+
|
20 |
+
const vertex = new Vector3();
|
21 |
+
const color = new Color();
|
22 |
+
const normal = new Vector3();
|
23 |
+
const uv = new Vector2();
|
24 |
+
|
25 |
+
const face = [];
|
26 |
+
|
27 |
+
function parseMesh( mesh ) {
|
28 |
+
|
29 |
+
let nbVertex = 0;
|
30 |
+
let nbNormals = 0;
|
31 |
+
let nbVertexUvs = 0;
|
32 |
+
|
33 |
+
const geometry = mesh.geometry;
|
34 |
+
|
35 |
+
const normalMatrixWorld = new Matrix3();
|
36 |
+
|
37 |
+
// shortcuts
|
38 |
+
const vertices = geometry.getAttribute( 'position' );
|
39 |
+
const normals = geometry.getAttribute( 'normal' );
|
40 |
+
const uvs = geometry.getAttribute( 'uv' );
|
41 |
+
const indices = geometry.getIndex();
|
42 |
+
|
43 |
+
// name of the mesh object
|
44 |
+
output += 'o ' + mesh.name + '\n';
|
45 |
+
|
46 |
+
// name of the mesh material
|
47 |
+
if ( mesh.material && mesh.material.name ) {
|
48 |
+
|
49 |
+
output += 'usemtl ' + mesh.material.name + '\n';
|
50 |
+
|
51 |
+
}
|
52 |
+
|
53 |
+
// vertices
|
54 |
+
|
55 |
+
if ( vertices !== undefined ) {
|
56 |
+
|
57 |
+
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
|
58 |
+
|
59 |
+
vertex.fromBufferAttribute( vertices, i );
|
60 |
+
|
61 |
+
// transform the vertex to world space
|
62 |
+
vertex.applyMatrix4( mesh.matrixWorld );
|
63 |
+
|
64 |
+
// transform the vertex to export format
|
65 |
+
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
|
66 |
+
|
67 |
+
}
|
68 |
+
|
69 |
+
}
|
70 |
+
|
71 |
+
// uvs
|
72 |
+
|
73 |
+
if ( uvs !== undefined ) {
|
74 |
+
|
75 |
+
for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) {
|
76 |
+
|
77 |
+
uv.fromBufferAttribute( uvs, i );
|
78 |
+
|
79 |
+
// transform the uv to export format
|
80 |
+
output += 'vt ' + uv.x + ' ' + uv.y + '\n';
|
81 |
+
|
82 |
+
}
|
83 |
+
|
84 |
+
}
|
85 |
+
|
86 |
+
// normals
|
87 |
+
|
88 |
+
if ( normals !== undefined ) {
|
89 |
+
|
90 |
+
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
|
91 |
+
|
92 |
+
for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
|
93 |
+
|
94 |
+
normal.fromBufferAttribute( normals, i );
|
95 |
+
|
96 |
+
// transform the normal to world space
|
97 |
+
normal.applyMatrix3( normalMatrixWorld ).normalize();
|
98 |
+
|
99 |
+
// transform the normal to export format
|
100 |
+
output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
|
101 |
+
|
102 |
+
}
|
103 |
+
|
104 |
+
}
|
105 |
+
|
106 |
+
// faces
|
107 |
+
|
108 |
+
if ( indices !== null ) {
|
109 |
+
|
110 |
+
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
|
111 |
+
|
112 |
+
for ( let m = 0; m < 3; m ++ ) {
|
113 |
+
|
114 |
+
const j = indices.getX( i + m ) + 1;
|
115 |
+
|
116 |
+
face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
|
117 |
+
|
118 |
+
}
|
119 |
+
|
120 |
+
// transform the face to export format
|
121 |
+
output += 'f ' + face.join( ' ' ) + '\n';
|
122 |
+
|
123 |
+
}
|
124 |
+
|
125 |
+
} else {
|
126 |
+
|
127 |
+
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
|
128 |
+
|
129 |
+
for ( let m = 0; m < 3; m ++ ) {
|
130 |
+
|
131 |
+
const j = i + m + 1;
|
132 |
+
|
133 |
+
face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
|
134 |
+
|
135 |
+
}
|
136 |
+
|
137 |
+
// transform the face to export format
|
138 |
+
output += 'f ' + face.join( ' ' ) + '\n';
|
139 |
+
|
140 |
+
}
|
141 |
+
|
142 |
+
}
|
143 |
+
|
144 |
+
// update index
|
145 |
+
indexVertex += nbVertex;
|
146 |
+
indexVertexUvs += nbVertexUvs;
|
147 |
+
indexNormals += nbNormals;
|
148 |
+
|
149 |
+
}
|
150 |
+
|
151 |
+
function parseLine( line ) {
|
152 |
+
|
153 |
+
let nbVertex = 0;
|
154 |
+
|
155 |
+
const geometry = line.geometry;
|
156 |
+
const type = line.type;
|
157 |
+
|
158 |
+
// shortcuts
|
159 |
+
const vertices = geometry.getAttribute( 'position' );
|
160 |
+
|
161 |
+
// name of the line object
|
162 |
+
output += 'o ' + line.name + '\n';
|
163 |
+
|
164 |
+
if ( vertices !== undefined ) {
|
165 |
+
|
166 |
+
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
|
167 |
+
|
168 |
+
vertex.fromBufferAttribute( vertices, i );
|
169 |
+
|
170 |
+
// transform the vertex to world space
|
171 |
+
vertex.applyMatrix4( line.matrixWorld );
|
172 |
+
|
173 |
+
// transform the vertex to export format
|
174 |
+
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
|
175 |
+
|
176 |
+
}
|
177 |
+
|
178 |
+
}
|
179 |
+
|
180 |
+
if ( type === 'Line' ) {
|
181 |
+
|
182 |
+
output += 'l ';
|
183 |
+
|
184 |
+
for ( let j = 1, l = vertices.count; j <= l; j ++ ) {
|
185 |
+
|
186 |
+
output += ( indexVertex + j ) + ' ';
|
187 |
+
|
188 |
+
}
|
189 |
+
|
190 |
+
output += '\n';
|
191 |
+
|
192 |
+
}
|
193 |
+
|
194 |
+
if ( type === 'LineSegments' ) {
|
195 |
+
|
196 |
+
for ( let j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) {
|
197 |
+
|
198 |
+
output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n';
|
199 |
+
|
200 |
+
}
|
201 |
+
|
202 |
+
}
|
203 |
+
|
204 |
+
// update index
|
205 |
+
indexVertex += nbVertex;
|
206 |
+
|
207 |
+
}
|
208 |
+
|
209 |
+
function parsePoints( points ) {
|
210 |
+
|
211 |
+
let nbVertex = 0;
|
212 |
+
|
213 |
+
const geometry = points.geometry;
|
214 |
+
|
215 |
+
const vertices = geometry.getAttribute( 'position' );
|
216 |
+
const colors = geometry.getAttribute( 'color' );
|
217 |
+
|
218 |
+
output += 'o ' + points.name + '\n';
|
219 |
+
|
220 |
+
if ( vertices !== undefined ) {
|
221 |
+
|
222 |
+
for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
|
223 |
+
|
224 |
+
vertex.fromBufferAttribute( vertices, i );
|
225 |
+
vertex.applyMatrix4( points.matrixWorld );
|
226 |
+
|
227 |
+
output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z;
|
228 |
+
|
229 |
+
if ( colors !== undefined ) {
|
230 |
+
|
231 |
+
color.fromBufferAttribute( colors, i );
|
232 |
+
|
233 |
+
ColorManagement.fromWorkingColorSpace( color, SRGBColorSpace );
|
234 |
+
|
235 |
+
output += ' ' + color.r + ' ' + color.g + ' ' + color.b;
|
236 |
+
|
237 |
+
}
|
238 |
+
|
239 |
+
output += '\n';
|
240 |
+
|
241 |
+
}
|
242 |
+
|
243 |
+
output += 'p ';
|
244 |
+
|
245 |
+
for ( let j = 1, l = vertices.count; j <= l; j ++ ) {
|
246 |
+
|
247 |
+
output += ( indexVertex + j ) + ' ';
|
248 |
+
|
249 |
+
}
|
250 |
+
|
251 |
+
output += '\n';
|
252 |
+
|
253 |
+
}
|
254 |
+
|
255 |
+
// update index
|
256 |
+
indexVertex += nbVertex;
|
257 |
+
|
258 |
+
}
|
259 |
+
|
260 |
+
object.traverse( function ( child ) {
|
261 |
+
|
262 |
+
if ( child.isMesh === true ) {
|
263 |
+
|
264 |
+
parseMesh( child );
|
265 |
+
|
266 |
+
}
|
267 |
+
|
268 |
+
if ( child.isLine === true ) {
|
269 |
+
|
270 |
+
parseLine( child );
|
271 |
+
|
272 |
+
}
|
273 |
+
|
274 |
+
if ( child.isPoints === true ) {
|
275 |
+
|
276 |
+
parsePoints( child );
|
277 |
+
|
278 |
+
}
|
279 |
+
|
280 |
+
} );
|
281 |
+
|
282 |
+
return output;
|
283 |
+
|
284 |
+
}
|
285 |
+
|
286 |
+
}
|
287 |
+
|
288 |
+
export { OBJExporter };
|
libs/three.js/0.172.0/jsm/exporters/PLYExporter.js
ADDED
@@ -0,0 +1,530 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Matrix3,
|
3 |
+
Vector3,
|
4 |
+
Color,
|
5 |
+
ColorManagement,
|
6 |
+
SRGBColorSpace
|
7 |
+
} from 'three';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* https://github.com/gkjohnson/ply-exporter-js
|
11 |
+
*
|
12 |
+
* Usage:
|
13 |
+
* const exporter = new PLYExporter();
|
14 |
+
*
|
15 |
+
* // second argument is a list of options
|
16 |
+
* exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
|
17 |
+
*
|
18 |
+
* Format Definition:
|
19 |
+
* http://paulbourke.net/dataformats/ply/
|
20 |
+
*/
|
21 |
+
|
22 |
+
class PLYExporter {
|
23 |
+
|
24 |
+
parse( object, onDone, options = {} ) {
|
25 |
+
|
26 |
+
// Iterate over the valid meshes in the object
|
27 |
+
function traverseMeshes( cb ) {
|
28 |
+
|
29 |
+
object.traverse( function ( child ) {
|
30 |
+
|
31 |
+
if ( child.isMesh === true || child.isPoints ) {
|
32 |
+
|
33 |
+
const mesh = child;
|
34 |
+
const geometry = mesh.geometry;
|
35 |
+
|
36 |
+
if ( geometry.hasAttribute( 'position' ) === true ) {
|
37 |
+
|
38 |
+
cb( mesh, geometry );
|
39 |
+
|
40 |
+
}
|
41 |
+
|
42 |
+
}
|
43 |
+
|
44 |
+
} );
|
45 |
+
|
46 |
+
}
|
47 |
+
|
48 |
+
// Default options
|
49 |
+
const defaultOptions = {
|
50 |
+
binary: false,
|
51 |
+
excludeAttributes: [], // normal, uv, color, index
|
52 |
+
littleEndian: false
|
53 |
+
};
|
54 |
+
|
55 |
+
options = Object.assign( defaultOptions, options );
|
56 |
+
|
57 |
+
const excludeAttributes = options.excludeAttributes;
|
58 |
+
let includeIndices = true;
|
59 |
+
let includeNormals = false;
|
60 |
+
let includeColors = false;
|
61 |
+
let includeUVs = false;
|
62 |
+
|
63 |
+
// count the vertices, check which properties are used,
|
64 |
+
// and cache the BufferGeometry
|
65 |
+
let vertexCount = 0;
|
66 |
+
let faceCount = 0;
|
67 |
+
|
68 |
+
object.traverse( function ( child ) {
|
69 |
+
|
70 |
+
if ( child.isMesh === true ) {
|
71 |
+
|
72 |
+
const mesh = child;
|
73 |
+
const geometry = mesh.geometry;
|
74 |
+
|
75 |
+
const vertices = geometry.getAttribute( 'position' );
|
76 |
+
const normals = geometry.getAttribute( 'normal' );
|
77 |
+
const uvs = geometry.getAttribute( 'uv' );
|
78 |
+
const colors = geometry.getAttribute( 'color' );
|
79 |
+
const indices = geometry.getIndex();
|
80 |
+
|
81 |
+
if ( vertices === undefined ) {
|
82 |
+
|
83 |
+
return;
|
84 |
+
|
85 |
+
}
|
86 |
+
|
87 |
+
vertexCount += vertices.count;
|
88 |
+
faceCount += indices ? indices.count / 3 : vertices.count / 3;
|
89 |
+
|
90 |
+
if ( normals !== undefined ) includeNormals = true;
|
91 |
+
|
92 |
+
if ( uvs !== undefined ) includeUVs = true;
|
93 |
+
|
94 |
+
if ( colors !== undefined ) includeColors = true;
|
95 |
+
|
96 |
+
} else if ( child.isPoints ) {
|
97 |
+
|
98 |
+
const mesh = child;
|
99 |
+
const geometry = mesh.geometry;
|
100 |
+
|
101 |
+
const vertices = geometry.getAttribute( 'position' );
|
102 |
+
const normals = geometry.getAttribute( 'normal' );
|
103 |
+
const colors = geometry.getAttribute( 'color' );
|
104 |
+
|
105 |
+
vertexCount += vertices.count;
|
106 |
+
|
107 |
+
if ( normals !== undefined ) includeNormals = true;
|
108 |
+
|
109 |
+
if ( colors !== undefined ) includeColors = true;
|
110 |
+
|
111 |
+
includeIndices = false;
|
112 |
+
|
113 |
+
}
|
114 |
+
|
115 |
+
} );
|
116 |
+
|
117 |
+
const tempColor = new Color();
|
118 |
+
includeIndices = includeIndices && excludeAttributes.indexOf( 'index' ) === - 1;
|
119 |
+
includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1;
|
120 |
+
includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1;
|
121 |
+
includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1;
|
122 |
+
|
123 |
+
|
124 |
+
if ( includeIndices && faceCount !== Math.floor( faceCount ) ) {
|
125 |
+
|
126 |
+
// point cloud meshes will not have an index array and may not have a
|
127 |
+
// number of vertices that is divisible by 3 (and therefore representable
|
128 |
+
// as triangles)
|
129 |
+
console.error(
|
130 |
+
|
131 |
+
'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' +
|
132 |
+
'number of indices is not divisible by 3.'
|
133 |
+
|
134 |
+
);
|
135 |
+
|
136 |
+
return null;
|
137 |
+
|
138 |
+
}
|
139 |
+
|
140 |
+
const indexByteCount = 4;
|
141 |
+
|
142 |
+
let header =
|
143 |
+
'ply\n' +
|
144 |
+
`format ${ options.binary ? ( options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' ) : 'ascii' } 1.0\n` +
|
145 |
+
`element vertex ${vertexCount}\n` +
|
146 |
+
|
147 |
+
// position
|
148 |
+
'property float x\n' +
|
149 |
+
'property float y\n' +
|
150 |
+
'property float z\n';
|
151 |
+
|
152 |
+
if ( includeNormals === true ) {
|
153 |
+
|
154 |
+
// normal
|
155 |
+
header +=
|
156 |
+
'property float nx\n' +
|
157 |
+
'property float ny\n' +
|
158 |
+
'property float nz\n';
|
159 |
+
|
160 |
+
}
|
161 |
+
|
162 |
+
if ( includeUVs === true ) {
|
163 |
+
|
164 |
+
// uvs
|
165 |
+
header +=
|
166 |
+
'property float s\n' +
|
167 |
+
'property float t\n';
|
168 |
+
|
169 |
+
}
|
170 |
+
|
171 |
+
if ( includeColors === true ) {
|
172 |
+
|
173 |
+
// colors
|
174 |
+
header +=
|
175 |
+
'property uchar red\n' +
|
176 |
+
'property uchar green\n' +
|
177 |
+
'property uchar blue\n';
|
178 |
+
|
179 |
+
}
|
180 |
+
|
181 |
+
if ( includeIndices === true ) {
|
182 |
+
|
183 |
+
// faces
|
184 |
+
header +=
|
185 |
+
`element face ${faceCount}\n` +
|
186 |
+
'property list uchar int vertex_index\n';
|
187 |
+
|
188 |
+
}
|
189 |
+
|
190 |
+
header += 'end_header\n';
|
191 |
+
|
192 |
+
|
193 |
+
// Generate attribute data
|
194 |
+
const vertex = new Vector3();
|
195 |
+
const normalMatrixWorld = new Matrix3();
|
196 |
+
let result = null;
|
197 |
+
|
198 |
+
if ( options.binary === true ) {
|
199 |
+
|
200 |
+
// Binary File Generation
|
201 |
+
const headerBin = new TextEncoder().encode( header );
|
202 |
+
|
203 |
+
// 3 position values at 4 bytes
|
204 |
+
// 3 normal values at 4 bytes
|
205 |
+
// 3 color channels with 1 byte
|
206 |
+
// 2 uv values at 4 bytes
|
207 |
+
const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) );
|
208 |
+
|
209 |
+
// 1 byte shape descriptor
|
210 |
+
// 3 vertex indices at ${indexByteCount} bytes
|
211 |
+
const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0;
|
212 |
+
const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) );
|
213 |
+
new Uint8Array( output.buffer ).set( headerBin, 0 );
|
214 |
+
|
215 |
+
|
216 |
+
let vOffset = headerBin.length;
|
217 |
+
let fOffset = headerBin.length + vertexListLength;
|
218 |
+
let writtenVertices = 0;
|
219 |
+
traverseMeshes( function ( mesh, geometry ) {
|
220 |
+
|
221 |
+
const vertices = geometry.getAttribute( 'position' );
|
222 |
+
const normals = geometry.getAttribute( 'normal' );
|
223 |
+
const uvs = geometry.getAttribute( 'uv' );
|
224 |
+
const colors = geometry.getAttribute( 'color' );
|
225 |
+
const indices = geometry.getIndex();
|
226 |
+
|
227 |
+
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
|
228 |
+
|
229 |
+
for ( let i = 0, l = vertices.count; i < l; i ++ ) {
|
230 |
+
|
231 |
+
vertex.fromBufferAttribute( vertices, i );
|
232 |
+
|
233 |
+
vertex.applyMatrix4( mesh.matrixWorld );
|
234 |
+
|
235 |
+
|
236 |
+
// Position information
|
237 |
+
output.setFloat32( vOffset, vertex.x, options.littleEndian );
|
238 |
+
vOffset += 4;
|
239 |
+
|
240 |
+
output.setFloat32( vOffset, vertex.y, options.littleEndian );
|
241 |
+
vOffset += 4;
|
242 |
+
|
243 |
+
output.setFloat32( vOffset, vertex.z, options.littleEndian );
|
244 |
+
vOffset += 4;
|
245 |
+
|
246 |
+
// Normal information
|
247 |
+
if ( includeNormals === true ) {
|
248 |
+
|
249 |
+
if ( normals != null ) {
|
250 |
+
|
251 |
+
vertex.fromBufferAttribute( normals, i );
|
252 |
+
|
253 |
+
vertex.applyMatrix3( normalMatrixWorld ).normalize();
|
254 |
+
|
255 |
+
output.setFloat32( vOffset, vertex.x, options.littleEndian );
|
256 |
+
vOffset += 4;
|
257 |
+
|
258 |
+
output.setFloat32( vOffset, vertex.y, options.littleEndian );
|
259 |
+
vOffset += 4;
|
260 |
+
|
261 |
+
output.setFloat32( vOffset, vertex.z, options.littleEndian );
|
262 |
+
vOffset += 4;
|
263 |
+
|
264 |
+
} else {
|
265 |
+
|
266 |
+
output.setFloat32( vOffset, 0, options.littleEndian );
|
267 |
+
vOffset += 4;
|
268 |
+
|
269 |
+
output.setFloat32( vOffset, 0, options.littleEndian );
|
270 |
+
vOffset += 4;
|
271 |
+
|
272 |
+
output.setFloat32( vOffset, 0, options.littleEndian );
|
273 |
+
vOffset += 4;
|
274 |
+
|
275 |
+
}
|
276 |
+
|
277 |
+
}
|
278 |
+
|
279 |
+
// UV information
|
280 |
+
if ( includeUVs === true ) {
|
281 |
+
|
282 |
+
if ( uvs != null ) {
|
283 |
+
|
284 |
+
output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian );
|
285 |
+
vOffset += 4;
|
286 |
+
|
287 |
+
output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian );
|
288 |
+
vOffset += 4;
|
289 |
+
|
290 |
+
} else {
|
291 |
+
|
292 |
+
output.setFloat32( vOffset, 0, options.littleEndian );
|
293 |
+
vOffset += 4;
|
294 |
+
|
295 |
+
output.setFloat32( vOffset, 0, options.littleEndian );
|
296 |
+
vOffset += 4;
|
297 |
+
|
298 |
+
}
|
299 |
+
|
300 |
+
}
|
301 |
+
|
302 |
+
// Color information
|
303 |
+
if ( includeColors === true ) {
|
304 |
+
|
305 |
+
if ( colors != null ) {
|
306 |
+
|
307 |
+
tempColor.fromBufferAttribute( colors, i );
|
308 |
+
|
309 |
+
ColorManagement.fromWorkingColorSpace( tempColor, SRGBColorSpace );
|
310 |
+
|
311 |
+
output.setUint8( vOffset, Math.floor( tempColor.r * 255 ) );
|
312 |
+
vOffset += 1;
|
313 |
+
|
314 |
+
output.setUint8( vOffset, Math.floor( tempColor.g * 255 ) );
|
315 |
+
vOffset += 1;
|
316 |
+
|
317 |
+
output.setUint8( vOffset, Math.floor( tempColor.b * 255 ) );
|
318 |
+
vOffset += 1;
|
319 |
+
|
320 |
+
} else {
|
321 |
+
|
322 |
+
output.setUint8( vOffset, 255 );
|
323 |
+
vOffset += 1;
|
324 |
+
|
325 |
+
output.setUint8( vOffset, 255 );
|
326 |
+
vOffset += 1;
|
327 |
+
|
328 |
+
output.setUint8( vOffset, 255 );
|
329 |
+
vOffset += 1;
|
330 |
+
|
331 |
+
}
|
332 |
+
|
333 |
+
}
|
334 |
+
|
335 |
+
}
|
336 |
+
|
337 |
+
if ( includeIndices === true ) {
|
338 |
+
|
339 |
+
// Create the face list
|
340 |
+
|
341 |
+
if ( indices !== null ) {
|
342 |
+
|
343 |
+
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
|
344 |
+
|
345 |
+
output.setUint8( fOffset, 3 );
|
346 |
+
fOffset += 1;
|
347 |
+
|
348 |
+
output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian );
|
349 |
+
fOffset += indexByteCount;
|
350 |
+
|
351 |
+
output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian );
|
352 |
+
fOffset += indexByteCount;
|
353 |
+
|
354 |
+
output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian );
|
355 |
+
fOffset += indexByteCount;
|
356 |
+
|
357 |
+
}
|
358 |
+
|
359 |
+
} else {
|
360 |
+
|
361 |
+
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
|
362 |
+
|
363 |
+
output.setUint8( fOffset, 3 );
|
364 |
+
fOffset += 1;
|
365 |
+
|
366 |
+
output.setUint32( fOffset, writtenVertices + i, options.littleEndian );
|
367 |
+
fOffset += indexByteCount;
|
368 |
+
|
369 |
+
output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian );
|
370 |
+
fOffset += indexByteCount;
|
371 |
+
|
372 |
+
output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian );
|
373 |
+
fOffset += indexByteCount;
|
374 |
+
|
375 |
+
}
|
376 |
+
|
377 |
+
}
|
378 |
+
|
379 |
+
}
|
380 |
+
|
381 |
+
|
382 |
+
// Save the amount of verts we've already written so we can offset
|
383 |
+
// the face index on the next mesh
|
384 |
+
writtenVertices += vertices.count;
|
385 |
+
|
386 |
+
} );
|
387 |
+
|
388 |
+
result = output.buffer;
|
389 |
+
|
390 |
+
} else {
|
391 |
+
|
392 |
+
// Ascii File Generation
|
393 |
+
// count the number of vertices
|
394 |
+
let writtenVertices = 0;
|
395 |
+
let vertexList = '';
|
396 |
+
let faceList = '';
|
397 |
+
|
398 |
+
traverseMeshes( function ( mesh, geometry ) {
|
399 |
+
|
400 |
+
const vertices = geometry.getAttribute( 'position' );
|
401 |
+
const normals = geometry.getAttribute( 'normal' );
|
402 |
+
const uvs = geometry.getAttribute( 'uv' );
|
403 |
+
const colors = geometry.getAttribute( 'color' );
|
404 |
+
const indices = geometry.getIndex();
|
405 |
+
|
406 |
+
normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
|
407 |
+
|
408 |
+
// form each line
|
409 |
+
for ( let i = 0, l = vertices.count; i < l; i ++ ) {
|
410 |
+
|
411 |
+
vertex.fromBufferAttribute( vertices, i );
|
412 |
+
|
413 |
+
vertex.applyMatrix4( mesh.matrixWorld );
|
414 |
+
|
415 |
+
|
416 |
+
// Position information
|
417 |
+
let line =
|
418 |
+
vertex.x + ' ' +
|
419 |
+
vertex.y + ' ' +
|
420 |
+
vertex.z;
|
421 |
+
|
422 |
+
// Normal information
|
423 |
+
if ( includeNormals === true ) {
|
424 |
+
|
425 |
+
if ( normals != null ) {
|
426 |
+
|
427 |
+
vertex.fromBufferAttribute( normals, i );
|
428 |
+
|
429 |
+
vertex.applyMatrix3( normalMatrixWorld ).normalize();
|
430 |
+
|
431 |
+
line += ' ' +
|
432 |
+
vertex.x + ' ' +
|
433 |
+
vertex.y + ' ' +
|
434 |
+
vertex.z;
|
435 |
+
|
436 |
+
} else {
|
437 |
+
|
438 |
+
line += ' 0 0 0';
|
439 |
+
|
440 |
+
}
|
441 |
+
|
442 |
+
}
|
443 |
+
|
444 |
+
// UV information
|
445 |
+
if ( includeUVs === true ) {
|
446 |
+
|
447 |
+
if ( uvs != null ) {
|
448 |
+
|
449 |
+
line += ' ' +
|
450 |
+
uvs.getX( i ) + ' ' +
|
451 |
+
uvs.getY( i );
|
452 |
+
|
453 |
+
} else {
|
454 |
+
|
455 |
+
line += ' 0 0';
|
456 |
+
|
457 |
+
}
|
458 |
+
|
459 |
+
}
|
460 |
+
|
461 |
+
// Color information
|
462 |
+
if ( includeColors === true ) {
|
463 |
+
|
464 |
+
if ( colors != null ) {
|
465 |
+
|
466 |
+
tempColor.fromBufferAttribute( colors, i );
|
467 |
+
|
468 |
+
ColorManagement.fromWorkingColorSpace( tempColor, SRGBColorSpace );
|
469 |
+
|
470 |
+
line += ' ' +
|
471 |
+
Math.floor( tempColor.r * 255 ) + ' ' +
|
472 |
+
Math.floor( tempColor.g * 255 ) + ' ' +
|
473 |
+
Math.floor( tempColor.b * 255 );
|
474 |
+
|
475 |
+
} else {
|
476 |
+
|
477 |
+
line += ' 255 255 255';
|
478 |
+
|
479 |
+
}
|
480 |
+
|
481 |
+
}
|
482 |
+
|
483 |
+
vertexList += line + '\n';
|
484 |
+
|
485 |
+
}
|
486 |
+
|
487 |
+
// Create the face list
|
488 |
+
if ( includeIndices === true ) {
|
489 |
+
|
490 |
+
if ( indices !== null ) {
|
491 |
+
|
492 |
+
for ( let i = 0, l = indices.count; i < l; i += 3 ) {
|
493 |
+
|
494 |
+
faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`;
|
495 |
+
faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`;
|
496 |
+
faceList += ` ${ indices.getX( i + 2 ) + writtenVertices }\n`;
|
497 |
+
|
498 |
+
}
|
499 |
+
|
500 |
+
} else {
|
501 |
+
|
502 |
+
for ( let i = 0, l = vertices.count; i < l; i += 3 ) {
|
503 |
+
|
504 |
+
faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`;
|
505 |
+
|
506 |
+
}
|
507 |
+
|
508 |
+
}
|
509 |
+
|
510 |
+
faceCount += indices ? indices.count / 3 : vertices.count / 3;
|
511 |
+
|
512 |
+
}
|
513 |
+
|
514 |
+
writtenVertices += vertices.count;
|
515 |
+
|
516 |
+
} );
|
517 |
+
|
518 |
+
result = `${ header }${vertexList}${ includeIndices ? `${faceList}\n` : '\n' }`;
|
519 |
+
|
520 |
+
}
|
521 |
+
|
522 |
+
if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) );
|
523 |
+
|
524 |
+
return result;
|
525 |
+
|
526 |
+
}
|
527 |
+
|
528 |
+
}
|
529 |
+
|
530 |
+
export { PLYExporter };
|
libs/three.js/0.172.0/jsm/exporters/STLExporter.js
ADDED
@@ -0,0 +1,199 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Vector3 } from 'three';
|
2 |
+
|
3 |
+
/**
|
4 |
+
* Usage:
|
5 |
+
* const exporter = new STLExporter();
|
6 |
+
*
|
7 |
+
* // second argument is a list of options
|
8 |
+
* const data = exporter.parse( mesh, { binary: true } );
|
9 |
+
*
|
10 |
+
*/
|
11 |
+
|
12 |
+
class STLExporter {
|
13 |
+
|
14 |
+
parse( scene, options = {} ) {
|
15 |
+
|
16 |
+
options = Object.assign( {
|
17 |
+
binary: false
|
18 |
+
}, options );
|
19 |
+
|
20 |
+
const binary = options.binary;
|
21 |
+
|
22 |
+
//
|
23 |
+
|
24 |
+
const objects = [];
|
25 |
+
let triangles = 0;
|
26 |
+
|
27 |
+
scene.traverse( function ( object ) {
|
28 |
+
|
29 |
+
if ( object.isMesh ) {
|
30 |
+
|
31 |
+
const geometry = object.geometry;
|
32 |
+
|
33 |
+
const index = geometry.index;
|
34 |
+
const positionAttribute = geometry.getAttribute( 'position' );
|
35 |
+
|
36 |
+
triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 );
|
37 |
+
|
38 |
+
objects.push( {
|
39 |
+
object3d: object,
|
40 |
+
geometry: geometry
|
41 |
+
} );
|
42 |
+
|
43 |
+
}
|
44 |
+
|
45 |
+
} );
|
46 |
+
|
47 |
+
let output;
|
48 |
+
let offset = 80; // skip header
|
49 |
+
|
50 |
+
if ( binary === true ) {
|
51 |
+
|
52 |
+
const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
|
53 |
+
const arrayBuffer = new ArrayBuffer( bufferLength );
|
54 |
+
output = new DataView( arrayBuffer );
|
55 |
+
output.setUint32( offset, triangles, true ); offset += 4;
|
56 |
+
|
57 |
+
} else {
|
58 |
+
|
59 |
+
output = '';
|
60 |
+
output += 'solid exported\n';
|
61 |
+
|
62 |
+
}
|
63 |
+
|
64 |
+
const vA = new Vector3();
|
65 |
+
const vB = new Vector3();
|
66 |
+
const vC = new Vector3();
|
67 |
+
const cb = new Vector3();
|
68 |
+
const ab = new Vector3();
|
69 |
+
const normal = new Vector3();
|
70 |
+
|
71 |
+
for ( let i = 0, il = objects.length; i < il; i ++ ) {
|
72 |
+
|
73 |
+
const object = objects[ i ].object3d;
|
74 |
+
const geometry = objects[ i ].geometry;
|
75 |
+
|
76 |
+
const index = geometry.index;
|
77 |
+
const positionAttribute = geometry.getAttribute( 'position' );
|
78 |
+
|
79 |
+
if ( index !== null ) {
|
80 |
+
|
81 |
+
// indexed geometry
|
82 |
+
|
83 |
+
for ( let j = 0; j < index.count; j += 3 ) {
|
84 |
+
|
85 |
+
const a = index.getX( j + 0 );
|
86 |
+
const b = index.getX( j + 1 );
|
87 |
+
const c = index.getX( j + 2 );
|
88 |
+
|
89 |
+
writeFace( a, b, c, positionAttribute, object );
|
90 |
+
|
91 |
+
}
|
92 |
+
|
93 |
+
} else {
|
94 |
+
|
95 |
+
// non-indexed geometry
|
96 |
+
|
97 |
+
for ( let j = 0; j < positionAttribute.count; j += 3 ) {
|
98 |
+
|
99 |
+
const a = j + 0;
|
100 |
+
const b = j + 1;
|
101 |
+
const c = j + 2;
|
102 |
+
|
103 |
+
writeFace( a, b, c, positionAttribute, object );
|
104 |
+
|
105 |
+
}
|
106 |
+
|
107 |
+
}
|
108 |
+
|
109 |
+
}
|
110 |
+
|
111 |
+
if ( binary === false ) {
|
112 |
+
|
113 |
+
output += 'endsolid exported\n';
|
114 |
+
|
115 |
+
}
|
116 |
+
|
117 |
+
return output;
|
118 |
+
|
119 |
+
function writeFace( a, b, c, positionAttribute, object ) {
|
120 |
+
|
121 |
+
vA.fromBufferAttribute( positionAttribute, a );
|
122 |
+
vB.fromBufferAttribute( positionAttribute, b );
|
123 |
+
vC.fromBufferAttribute( positionAttribute, c );
|
124 |
+
|
125 |
+
if ( object.isSkinnedMesh === true ) {
|
126 |
+
|
127 |
+
object.applyBoneTransform( a, vA );
|
128 |
+
object.applyBoneTransform( b, vB );
|
129 |
+
object.applyBoneTransform( c, vC );
|
130 |
+
|
131 |
+
}
|
132 |
+
|
133 |
+
vA.applyMatrix4( object.matrixWorld );
|
134 |
+
vB.applyMatrix4( object.matrixWorld );
|
135 |
+
vC.applyMatrix4( object.matrixWorld );
|
136 |
+
|
137 |
+
writeNormal( vA, vB, vC );
|
138 |
+
|
139 |
+
writeVertex( vA );
|
140 |
+
writeVertex( vB );
|
141 |
+
writeVertex( vC );
|
142 |
+
|
143 |
+
if ( binary === true ) {
|
144 |
+
|
145 |
+
output.setUint16( offset, 0, true ); offset += 2;
|
146 |
+
|
147 |
+
} else {
|
148 |
+
|
149 |
+
output += '\t\tendloop\n';
|
150 |
+
output += '\tendfacet\n';
|
151 |
+
|
152 |
+
}
|
153 |
+
|
154 |
+
}
|
155 |
+
|
156 |
+
function writeNormal( vA, vB, vC ) {
|
157 |
+
|
158 |
+
cb.subVectors( vC, vB );
|
159 |
+
ab.subVectors( vA, vB );
|
160 |
+
cb.cross( ab ).normalize();
|
161 |
+
|
162 |
+
normal.copy( cb ).normalize();
|
163 |
+
|
164 |
+
if ( binary === true ) {
|
165 |
+
|
166 |
+
output.setFloat32( offset, normal.x, true ); offset += 4;
|
167 |
+
output.setFloat32( offset, normal.y, true ); offset += 4;
|
168 |
+
output.setFloat32( offset, normal.z, true ); offset += 4;
|
169 |
+
|
170 |
+
} else {
|
171 |
+
|
172 |
+
output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
|
173 |
+
output += '\t\touter loop\n';
|
174 |
+
|
175 |
+
}
|
176 |
+
|
177 |
+
}
|
178 |
+
|
179 |
+
function writeVertex( vertex ) {
|
180 |
+
|
181 |
+
if ( binary === true ) {
|
182 |
+
|
183 |
+
output.setFloat32( offset, vertex.x, true ); offset += 4;
|
184 |
+
output.setFloat32( offset, vertex.y, true ); offset += 4;
|
185 |
+
output.setFloat32( offset, vertex.z, true ); offset += 4;
|
186 |
+
|
187 |
+
} else {
|
188 |
+
|
189 |
+
output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
|
190 |
+
|
191 |
+
}
|
192 |
+
|
193 |
+
}
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
export { STLExporter };
|
libs/three.js/0.172.0/jsm/exporters/USDZExporter.js
ADDED
@@ -0,0 +1,782 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
NoColorSpace,
|
3 |
+
DoubleSide,
|
4 |
+
Color,
|
5 |
+
} from 'three';
|
6 |
+
|
7 |
+
import {
|
8 |
+
strToU8,
|
9 |
+
zipSync,
|
10 |
+
} from '../libs/fflate.module.js';
|
11 |
+
|
12 |
+
class USDZExporter {
|
13 |
+
|
14 |
+
constructor() {
|
15 |
+
|
16 |
+
this.textureUtils = null;
|
17 |
+
|
18 |
+
}
|
19 |
+
|
20 |
+
setTextureUtils( utils ) {
|
21 |
+
|
22 |
+
this.textureUtils = utils;
|
23 |
+
|
24 |
+
}
|
25 |
+
|
26 |
+
parse( scene, onDone, onError, options ) {
|
27 |
+
|
28 |
+
this.parseAsync( scene, options ).then( onDone ).catch( onError );
|
29 |
+
|
30 |
+
}
|
31 |
+
|
32 |
+
async parseAsync( scene, options = {} ) {
|
33 |
+
|
34 |
+
options = Object.assign( {
|
35 |
+
ar: {
|
36 |
+
anchoring: { type: 'plane' },
|
37 |
+
planeAnchoring: { alignment: 'horizontal' }
|
38 |
+
},
|
39 |
+
includeAnchoringProperties: true,
|
40 |
+
quickLookCompatible: false,
|
41 |
+
maxTextureSize: 1024,
|
42 |
+
}, options );
|
43 |
+
|
44 |
+
const files = {};
|
45 |
+
const modelFileName = 'model.usda';
|
46 |
+
|
47 |
+
// model file should be first in USDZ archive so we init it here
|
48 |
+
files[ modelFileName ] = null;
|
49 |
+
|
50 |
+
let output = buildHeader();
|
51 |
+
|
52 |
+
output += buildSceneStart( options );
|
53 |
+
|
54 |
+
const materials = {};
|
55 |
+
const textures = {};
|
56 |
+
|
57 |
+
scene.traverseVisible( ( object ) => {
|
58 |
+
|
59 |
+
if ( object.isMesh ) {
|
60 |
+
|
61 |
+
const geometry = object.geometry;
|
62 |
+
const material = object.material;
|
63 |
+
|
64 |
+
if ( material.isMeshStandardMaterial ) {
|
65 |
+
|
66 |
+
const geometryFileName = 'geometries/Geometry_' + geometry.id + '.usda';
|
67 |
+
|
68 |
+
if ( ! ( geometryFileName in files ) ) {
|
69 |
+
|
70 |
+
const meshObject = buildMeshObject( geometry );
|
71 |
+
files[ geometryFileName ] = buildUSDFileAsString( meshObject );
|
72 |
+
|
73 |
+
}
|
74 |
+
|
75 |
+
if ( ! ( material.uuid in materials ) ) {
|
76 |
+
|
77 |
+
materials[ material.uuid ] = material;
|
78 |
+
|
79 |
+
}
|
80 |
+
|
81 |
+
output += buildXform( object, geometry, material );
|
82 |
+
|
83 |
+
} else {
|
84 |
+
|
85 |
+
console.warn( 'THREE.USDZExporter: Unsupported material type (USDZ only supports MeshStandardMaterial)', object );
|
86 |
+
|
87 |
+
}
|
88 |
+
|
89 |
+
} else if ( object.isCamera ) {
|
90 |
+
|
91 |
+
output += buildCamera( object );
|
92 |
+
|
93 |
+
}
|
94 |
+
|
95 |
+
} );
|
96 |
+
|
97 |
+
|
98 |
+
output += buildSceneEnd();
|
99 |
+
|
100 |
+
output += buildMaterials( materials, textures, options.quickLookCompatible );
|
101 |
+
|
102 |
+
files[ modelFileName ] = strToU8( output );
|
103 |
+
output = null;
|
104 |
+
|
105 |
+
for ( const id in textures ) {
|
106 |
+
|
107 |
+
let texture = textures[ id ];
|
108 |
+
|
109 |
+
if ( texture.isCompressedTexture === true ) {
|
110 |
+
|
111 |
+
if ( this.textureUtils === null ) {
|
112 |
+
|
113 |
+
throw new Error( 'THREE.USDZExporter: setTextureUtils() must be called to process compressed textures.' );
|
114 |
+
|
115 |
+
} else {
|
116 |
+
|
117 |
+
texture = await this.textureUtils.decompress( texture );
|
118 |
+
|
119 |
+
}
|
120 |
+
|
121 |
+
}
|
122 |
+
|
123 |
+
const canvas = imageToCanvas( texture.image, texture.flipY, options.maxTextureSize );
|
124 |
+
const blob = await new Promise( resolve => canvas.toBlob( resolve, 'image/png', 1 ) );
|
125 |
+
|
126 |
+
files[ `textures/Texture_${ id }.png` ] = new Uint8Array( await blob.arrayBuffer() );
|
127 |
+
|
128 |
+
}
|
129 |
+
|
130 |
+
// 64 byte alignment
|
131 |
+
// https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109
|
132 |
+
|
133 |
+
let offset = 0;
|
134 |
+
|
135 |
+
for ( const filename in files ) {
|
136 |
+
|
137 |
+
const file = files[ filename ];
|
138 |
+
const headerSize = 34 + filename.length;
|
139 |
+
|
140 |
+
offset += headerSize;
|
141 |
+
|
142 |
+
const offsetMod64 = offset & 63;
|
143 |
+
|
144 |
+
if ( offsetMod64 !== 4 ) {
|
145 |
+
|
146 |
+
const padLength = 64 - offsetMod64;
|
147 |
+
const padding = new Uint8Array( padLength );
|
148 |
+
|
149 |
+
files[ filename ] = [ file, { extra: { 12345: padding } } ];
|
150 |
+
|
151 |
+
}
|
152 |
+
|
153 |
+
offset = file.length;
|
154 |
+
|
155 |
+
}
|
156 |
+
|
157 |
+
return zipSync( files, { level: 0 } );
|
158 |
+
|
159 |
+
}
|
160 |
+
|
161 |
+
}
|
162 |
+
|
163 |
+
function imageToCanvas( image, flipY, maxTextureSize ) {
|
164 |
+
|
165 |
+
if ( ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement ) ||
|
166 |
+
( typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement ) ||
|
167 |
+
( typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas ) ||
|
168 |
+
( typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) ) {
|
169 |
+
|
170 |
+
const scale = maxTextureSize / Math.max( image.width, image.height );
|
171 |
+
|
172 |
+
const canvas = document.createElement( 'canvas' );
|
173 |
+
canvas.width = image.width * Math.min( 1, scale );
|
174 |
+
canvas.height = image.height * Math.min( 1, scale );
|
175 |
+
|
176 |
+
const context = canvas.getContext( '2d' );
|
177 |
+
|
178 |
+
// TODO: We should be able to do this in the UsdTransform2d?
|
179 |
+
|
180 |
+
if ( flipY === true ) {
|
181 |
+
|
182 |
+
context.translate( 0, canvas.height );
|
183 |
+
context.scale( 1, - 1 );
|
184 |
+
|
185 |
+
}
|
186 |
+
|
187 |
+
context.drawImage( image, 0, 0, canvas.width, canvas.height );
|
188 |
+
|
189 |
+
return canvas;
|
190 |
+
|
191 |
+
} else {
|
192 |
+
|
193 |
+
throw new Error( 'THREE.USDZExporter: No valid image data found. Unable to process texture.' );
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
}
|
198 |
+
|
199 |
+
//
|
200 |
+
|
201 |
+
const PRECISION = 7;
|
202 |
+
|
203 |
+
function buildHeader() {
|
204 |
+
|
205 |
+
return `#usda 1.0
|
206 |
+
(
|
207 |
+
customLayerData = {
|
208 |
+
string creator = "Three.js USDZExporter"
|
209 |
+
}
|
210 |
+
defaultPrim = "Root"
|
211 |
+
metersPerUnit = 1
|
212 |
+
upAxis = "Y"
|
213 |
+
)
|
214 |
+
|
215 |
+
`;
|
216 |
+
|
217 |
+
}
|
218 |
+
|
219 |
+
function buildSceneStart( options ) {
|
220 |
+
|
221 |
+
const alignment = options.includeAnchoringProperties === true ? `
|
222 |
+
token preliminary:anchoring:type = "${options.ar.anchoring.type}"
|
223 |
+
token preliminary:planeAnchoring:alignment = "${options.ar.planeAnchoring.alignment}"
|
224 |
+
` : '';
|
225 |
+
return `def Xform "Root"
|
226 |
+
{
|
227 |
+
def Scope "Scenes" (
|
228 |
+
kind = "sceneLibrary"
|
229 |
+
)
|
230 |
+
{
|
231 |
+
def Xform "Scene" (
|
232 |
+
customData = {
|
233 |
+
bool preliminary_collidesWithEnvironment = 0
|
234 |
+
string sceneName = "Scene"
|
235 |
+
}
|
236 |
+
sceneName = "Scene"
|
237 |
+
)
|
238 |
+
{${alignment}
|
239 |
+
`;
|
240 |
+
|
241 |
+
}
|
242 |
+
|
243 |
+
function buildSceneEnd() {
|
244 |
+
|
245 |
+
return `
|
246 |
+
}
|
247 |
+
}
|
248 |
+
}
|
249 |
+
|
250 |
+
`;
|
251 |
+
|
252 |
+
}
|
253 |
+
|
254 |
+
function buildUSDFileAsString( dataToInsert ) {
|
255 |
+
|
256 |
+
let output = buildHeader();
|
257 |
+
output += dataToInsert;
|
258 |
+
return strToU8( output );
|
259 |
+
|
260 |
+
}
|
261 |
+
|
262 |
+
// Xform
|
263 |
+
|
264 |
+
function buildXform( object, geometry, material ) {
|
265 |
+
|
266 |
+
const name = 'Object_' + object.id;
|
267 |
+
const transform = buildMatrix( object.matrixWorld );
|
268 |
+
|
269 |
+
if ( object.matrixWorld.determinant() < 0 ) {
|
270 |
+
|
271 |
+
console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', object );
|
272 |
+
|
273 |
+
}
|
274 |
+
|
275 |
+
return `def Xform "${ name }" (
|
276 |
+
prepend references = @./geometries/Geometry_${ geometry.id }.usda@</Geometry>
|
277 |
+
prepend apiSchemas = ["MaterialBindingAPI"]
|
278 |
+
)
|
279 |
+
{
|
280 |
+
matrix4d xformOp:transform = ${ transform }
|
281 |
+
uniform token[] xformOpOrder = ["xformOp:transform"]
|
282 |
+
|
283 |
+
rel material:binding = </Materials/Material_${ material.id }>
|
284 |
+
}
|
285 |
+
|
286 |
+
`;
|
287 |
+
|
288 |
+
}
|
289 |
+
|
290 |
+
function buildMatrix( matrix ) {
|
291 |
+
|
292 |
+
const array = matrix.elements;
|
293 |
+
|
294 |
+
return `( ${ buildMatrixRow( array, 0 ) }, ${ buildMatrixRow( array, 4 ) }, ${ buildMatrixRow( array, 8 ) }, ${ buildMatrixRow( array, 12 ) } )`;
|
295 |
+
|
296 |
+
}
|
297 |
+
|
298 |
+
function buildMatrixRow( array, offset ) {
|
299 |
+
|
300 |
+
return `(${ array[ offset + 0 ] }, ${ array[ offset + 1 ] }, ${ array[ offset + 2 ] }, ${ array[ offset + 3 ] })`;
|
301 |
+
|
302 |
+
}
|
303 |
+
|
304 |
+
// Mesh
|
305 |
+
|
306 |
+
function buildMeshObject( geometry ) {
|
307 |
+
|
308 |
+
const mesh = buildMesh( geometry );
|
309 |
+
return `
|
310 |
+
def "Geometry"
|
311 |
+
{
|
312 |
+
${mesh}
|
313 |
+
}
|
314 |
+
`;
|
315 |
+
|
316 |
+
}
|
317 |
+
|
318 |
+
function buildMesh( geometry ) {
|
319 |
+
|
320 |
+
const name = 'Geometry';
|
321 |
+
const attributes = geometry.attributes;
|
322 |
+
const count = attributes.position.count;
|
323 |
+
|
324 |
+
return `
|
325 |
+
def Mesh "${ name }"
|
326 |
+
{
|
327 |
+
int[] faceVertexCounts = [${ buildMeshVertexCount( geometry ) }]
|
328 |
+
int[] faceVertexIndices = [${ buildMeshVertexIndices( geometry ) }]
|
329 |
+
normal3f[] normals = [${ buildVector3Array( attributes.normal, count )}] (
|
330 |
+
interpolation = "vertex"
|
331 |
+
)
|
332 |
+
point3f[] points = [${ buildVector3Array( attributes.position, count )}]
|
333 |
+
${ buildPrimvars( attributes ) }
|
334 |
+
uniform token subdivisionScheme = "none"
|
335 |
+
}
|
336 |
+
`;
|
337 |
+
|
338 |
+
}
|
339 |
+
|
340 |
+
function buildMeshVertexCount( geometry ) {
|
341 |
+
|
342 |
+
const count = geometry.index !== null ? geometry.index.count : geometry.attributes.position.count;
|
343 |
+
|
344 |
+
return Array( count / 3 ).fill( 3 ).join( ', ' );
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
function buildMeshVertexIndices( geometry ) {
|
349 |
+
|
350 |
+
const index = geometry.index;
|
351 |
+
const array = [];
|
352 |
+
|
353 |
+
if ( index !== null ) {
|
354 |
+
|
355 |
+
for ( let i = 0; i < index.count; i ++ ) {
|
356 |
+
|
357 |
+
array.push( index.getX( i ) );
|
358 |
+
|
359 |
+
}
|
360 |
+
|
361 |
+
} else {
|
362 |
+
|
363 |
+
const length = geometry.attributes.position.count;
|
364 |
+
|
365 |
+
for ( let i = 0; i < length; i ++ ) {
|
366 |
+
|
367 |
+
array.push( i );
|
368 |
+
|
369 |
+
}
|
370 |
+
|
371 |
+
}
|
372 |
+
|
373 |
+
return array.join( ', ' );
|
374 |
+
|
375 |
+
}
|
376 |
+
|
377 |
+
function buildVector3Array( attribute, count ) {
|
378 |
+
|
379 |
+
if ( attribute === undefined ) {
|
380 |
+
|
381 |
+
console.warn( 'USDZExporter: Normals missing.' );
|
382 |
+
return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );
|
383 |
+
|
384 |
+
}
|
385 |
+
|
386 |
+
const array = [];
|
387 |
+
|
388 |
+
for ( let i = 0; i < attribute.count; i ++ ) {
|
389 |
+
|
390 |
+
const x = attribute.getX( i );
|
391 |
+
const y = attribute.getY( i );
|
392 |
+
const z = attribute.getZ( i );
|
393 |
+
|
394 |
+
array.push( `(${ x.toPrecision( PRECISION ) }, ${ y.toPrecision( PRECISION ) }, ${ z.toPrecision( PRECISION ) })` );
|
395 |
+
|
396 |
+
}
|
397 |
+
|
398 |
+
return array.join( ', ' );
|
399 |
+
|
400 |
+
}
|
401 |
+
|
402 |
+
function buildVector2Array( attribute ) {
|
403 |
+
|
404 |
+
const array = [];
|
405 |
+
|
406 |
+
for ( let i = 0; i < attribute.count; i ++ ) {
|
407 |
+
|
408 |
+
const x = attribute.getX( i );
|
409 |
+
const y = attribute.getY( i );
|
410 |
+
|
411 |
+
array.push( `(${ x.toPrecision( PRECISION ) }, ${ 1 - y.toPrecision( PRECISION ) })` );
|
412 |
+
|
413 |
+
}
|
414 |
+
|
415 |
+
return array.join( ', ' );
|
416 |
+
|
417 |
+
}
|
418 |
+
|
419 |
+
function buildPrimvars( attributes ) {
|
420 |
+
|
421 |
+
let string = '';
|
422 |
+
|
423 |
+
for ( let i = 0; i < 4; i ++ ) {
|
424 |
+
|
425 |
+
const id = ( i > 0 ? i : '' );
|
426 |
+
const attribute = attributes[ 'uv' + id ];
|
427 |
+
|
428 |
+
if ( attribute !== undefined ) {
|
429 |
+
|
430 |
+
string += `
|
431 |
+
texCoord2f[] primvars:st${ id } = [${ buildVector2Array( attribute )}] (
|
432 |
+
interpolation = "vertex"
|
433 |
+
)`;
|
434 |
+
|
435 |
+
}
|
436 |
+
|
437 |
+
}
|
438 |
+
|
439 |
+
// vertex colors
|
440 |
+
|
441 |
+
const colorAttribute = attributes.color;
|
442 |
+
|
443 |
+
if ( colorAttribute !== undefined ) {
|
444 |
+
|
445 |
+
const count = colorAttribute.count;
|
446 |
+
|
447 |
+
string += `
|
448 |
+
color3f[] primvars:displayColor = [${buildVector3Array( colorAttribute, count )}] (
|
449 |
+
interpolation = "vertex"
|
450 |
+
)`;
|
451 |
+
|
452 |
+
}
|
453 |
+
|
454 |
+
return string;
|
455 |
+
|
456 |
+
}
|
457 |
+
|
458 |
+
// Materials
|
459 |
+
|
460 |
+
function buildMaterials( materials, textures, quickLookCompatible = false ) {
|
461 |
+
|
462 |
+
const array = [];
|
463 |
+
|
464 |
+
for ( const uuid in materials ) {
|
465 |
+
|
466 |
+
const material = materials[ uuid ];
|
467 |
+
|
468 |
+
array.push( buildMaterial( material, textures, quickLookCompatible ) );
|
469 |
+
|
470 |
+
}
|
471 |
+
|
472 |
+
return `def "Materials"
|
473 |
+
{
|
474 |
+
${ array.join( '' ) }
|
475 |
+
}
|
476 |
+
|
477 |
+
`;
|
478 |
+
|
479 |
+
}
|
480 |
+
|
481 |
+
function buildMaterial( material, textures, quickLookCompatible = false ) {
|
482 |
+
|
483 |
+
// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
|
484 |
+
|
485 |
+
const pad = ' ';
|
486 |
+
const inputs = [];
|
487 |
+
const samplers = [];
|
488 |
+
|
489 |
+
function buildTexture( texture, mapType, color ) {
|
490 |
+
|
491 |
+
const id = texture.source.id + '_' + texture.flipY;
|
492 |
+
|
493 |
+
textures[ id ] = texture;
|
494 |
+
|
495 |
+
const uv = texture.channel > 0 ? 'st' + texture.channel : 'st';
|
496 |
+
|
497 |
+
const WRAPPINGS = {
|
498 |
+
1000: 'repeat', // RepeatWrapping
|
499 |
+
1001: 'clamp', // ClampToEdgeWrapping
|
500 |
+
1002: 'mirror' // MirroredRepeatWrapping
|
501 |
+
};
|
502 |
+
|
503 |
+
const repeat = texture.repeat.clone();
|
504 |
+
const offset = texture.offset.clone();
|
505 |
+
const rotation = texture.rotation;
|
506 |
+
|
507 |
+
// rotation is around the wrong point. after rotation we need to shift offset again so that we're rotating around the right spot
|
508 |
+
const xRotationOffset = Math.sin( rotation );
|
509 |
+
const yRotationOffset = Math.cos( rotation );
|
510 |
+
|
511 |
+
// texture coordinates start in the opposite corner, need to correct
|
512 |
+
offset.y = 1 - offset.y - repeat.y;
|
513 |
+
|
514 |
+
// turns out QuickLook is buggy and interprets texture repeat inverted/applies operations in a different order.
|
515 |
+
// Apple Feedback: FB10036297 and FB11442287
|
516 |
+
if ( quickLookCompatible ) {
|
517 |
+
|
518 |
+
// This is NOT correct yet in QuickLook, but comes close for a range of models.
|
519 |
+
// It becomes more incorrect the bigger the offset is
|
520 |
+
|
521 |
+
offset.x = offset.x / repeat.x;
|
522 |
+
offset.y = offset.y / repeat.y;
|
523 |
+
|
524 |
+
offset.x += xRotationOffset / repeat.x;
|
525 |
+
offset.y += yRotationOffset - 1;
|
526 |
+
|
527 |
+
} else {
|
528 |
+
|
529 |
+
// results match glTF results exactly. verified correct in usdview.
|
530 |
+
offset.x += xRotationOffset * repeat.x;
|
531 |
+
offset.y += ( 1 - yRotationOffset ) * repeat.y;
|
532 |
+
|
533 |
+
}
|
534 |
+
|
535 |
+
return `
|
536 |
+
def Shader "PrimvarReader_${ mapType }"
|
537 |
+
{
|
538 |
+
uniform token info:id = "UsdPrimvarReader_float2"
|
539 |
+
float2 inputs:fallback = (0.0, 0.0)
|
540 |
+
token inputs:varname = "${ uv }"
|
541 |
+
float2 outputs:result
|
542 |
+
}
|
543 |
+
|
544 |
+
def Shader "Transform2d_${ mapType }"
|
545 |
+
{
|
546 |
+
uniform token info:id = "UsdTransform2d"
|
547 |
+
token inputs:in.connect = </Materials/Material_${ material.id }/PrimvarReader_${ mapType }.outputs:result>
|
548 |
+
float inputs:rotation = ${ ( rotation * ( 180 / Math.PI ) ).toFixed( PRECISION ) }
|
549 |
+
float2 inputs:scale = ${ buildVector2( repeat ) }
|
550 |
+
float2 inputs:translation = ${ buildVector2( offset ) }
|
551 |
+
float2 outputs:result
|
552 |
+
}
|
553 |
+
|
554 |
+
def Shader "Texture_${ texture.id }_${ mapType }"
|
555 |
+
{
|
556 |
+
uniform token info:id = "UsdUVTexture"
|
557 |
+
asset inputs:file = @textures/Texture_${ id }.png@
|
558 |
+
float2 inputs:st.connect = </Materials/Material_${ material.id }/Transform2d_${ mapType }.outputs:result>
|
559 |
+
${ color !== undefined ? 'float4 inputs:scale = ' + buildColor4( color ) : '' }
|
560 |
+
token inputs:sourceColorSpace = "${ texture.colorSpace === NoColorSpace ? 'raw' : 'sRGB' }"
|
561 |
+
token inputs:wrapS = "${ WRAPPINGS[ texture.wrapS ] }"
|
562 |
+
token inputs:wrapT = "${ WRAPPINGS[ texture.wrapT ] }"
|
563 |
+
float outputs:r
|
564 |
+
float outputs:g
|
565 |
+
float outputs:b
|
566 |
+
float3 outputs:rgb
|
567 |
+
${ material.transparent || material.alphaTest > 0.0 ? 'float outputs:a' : '' }
|
568 |
+
}`;
|
569 |
+
|
570 |
+
}
|
571 |
+
|
572 |
+
|
573 |
+
if ( material.side === DoubleSide ) {
|
574 |
+
|
575 |
+
console.warn( 'THREE.USDZExporter: USDZ does not support double sided materials', material );
|
576 |
+
|
577 |
+
}
|
578 |
+
|
579 |
+
if ( material.map !== null ) {
|
580 |
+
|
581 |
+
inputs.push( `${ pad }color3f inputs:diffuseColor.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:rgb>` );
|
582 |
+
|
583 |
+
if ( material.transparent ) {
|
584 |
+
|
585 |
+
inputs.push( `${ pad }float inputs:opacity.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:a>` );
|
586 |
+
|
587 |
+
} else if ( material.alphaTest > 0.0 ) {
|
588 |
+
|
589 |
+
inputs.push( `${ pad }float inputs:opacity.connect = </Materials/Material_${ material.id }/Texture_${ material.map.id }_diffuse.outputs:a>` );
|
590 |
+
inputs.push( `${ pad }float inputs:opacityThreshold = ${material.alphaTest}` );
|
591 |
+
|
592 |
+
}
|
593 |
+
|
594 |
+
samplers.push( buildTexture( material.map, 'diffuse', material.color ) );
|
595 |
+
|
596 |
+
} else {
|
597 |
+
|
598 |
+
inputs.push( `${ pad }color3f inputs:diffuseColor = ${ buildColor( material.color ) }` );
|
599 |
+
|
600 |
+
}
|
601 |
+
|
602 |
+
if ( material.emissiveMap !== null ) {
|
603 |
+
|
604 |
+
inputs.push( `${ pad }color3f inputs:emissiveColor.connect = </Materials/Material_${ material.id }/Texture_${ material.emissiveMap.id }_emissive.outputs:rgb>` );
|
605 |
+
|
606 |
+
samplers.push( buildTexture( material.emissiveMap, 'emissive', new Color( material.emissive.r * material.emissiveIntensity, material.emissive.g * material.emissiveIntensity, material.emissive.b * material.emissiveIntensity ) ) );
|
607 |
+
|
608 |
+
} else if ( material.emissive.getHex() > 0 ) {
|
609 |
+
|
610 |
+
inputs.push( `${ pad }color3f inputs:emissiveColor = ${ buildColor( material.emissive ) }` );
|
611 |
+
|
612 |
+
}
|
613 |
+
|
614 |
+
if ( material.normalMap !== null ) {
|
615 |
+
|
616 |
+
inputs.push( `${ pad }normal3f inputs:normal.connect = </Materials/Material_${ material.id }/Texture_${ material.normalMap.id }_normal.outputs:rgb>` );
|
617 |
+
|
618 |
+
samplers.push( buildTexture( material.normalMap, 'normal' ) );
|
619 |
+
|
620 |
+
}
|
621 |
+
|
622 |
+
if ( material.aoMap !== null ) {
|
623 |
+
|
624 |
+
inputs.push( `${ pad }float inputs:occlusion.connect = </Materials/Material_${ material.id }/Texture_${ material.aoMap.id }_occlusion.outputs:r>` );
|
625 |
+
|
626 |
+
samplers.push( buildTexture( material.aoMap, 'occlusion', new Color( material.aoMapIntensity, material.aoMapIntensity, material.aoMapIntensity ) ) );
|
627 |
+
|
628 |
+
}
|
629 |
+
|
630 |
+
if ( material.roughnessMap !== null ) {
|
631 |
+
|
632 |
+
inputs.push( `${ pad }float inputs:roughness.connect = </Materials/Material_${ material.id }/Texture_${ material.roughnessMap.id }_roughness.outputs:g>` );
|
633 |
+
|
634 |
+
samplers.push( buildTexture( material.roughnessMap, 'roughness', new Color( material.roughness, material.roughness, material.roughness ) ) );
|
635 |
+
|
636 |
+
} else {
|
637 |
+
|
638 |
+
inputs.push( `${ pad }float inputs:roughness = ${ material.roughness }` );
|
639 |
+
|
640 |
+
}
|
641 |
+
|
642 |
+
if ( material.metalnessMap !== null ) {
|
643 |
+
|
644 |
+
inputs.push( `${ pad }float inputs:metallic.connect = </Materials/Material_${ material.id }/Texture_${ material.metalnessMap.id }_metallic.outputs:b>` );
|
645 |
+
|
646 |
+
samplers.push( buildTexture( material.metalnessMap, 'metallic', new Color( material.metalness, material.metalness, material.metalness ) ) );
|
647 |
+
|
648 |
+
} else {
|
649 |
+
|
650 |
+
inputs.push( `${ pad }float inputs:metallic = ${ material.metalness }` );
|
651 |
+
|
652 |
+
}
|
653 |
+
|
654 |
+
if ( material.alphaMap !== null ) {
|
655 |
+
|
656 |
+
inputs.push( `${pad}float inputs:opacity.connect = </Materials/Material_${material.id}/Texture_${material.alphaMap.id}_opacity.outputs:r>` );
|
657 |
+
inputs.push( `${pad}float inputs:opacityThreshold = 0.0001` );
|
658 |
+
|
659 |
+
samplers.push( buildTexture( material.alphaMap, 'opacity' ) );
|
660 |
+
|
661 |
+
} else {
|
662 |
+
|
663 |
+
inputs.push( `${pad}float inputs:opacity = ${material.opacity}` );
|
664 |
+
|
665 |
+
}
|
666 |
+
|
667 |
+
if ( material.isMeshPhysicalMaterial ) {
|
668 |
+
|
669 |
+
if ( material.clearcoatMap !== null ) {
|
670 |
+
|
671 |
+
inputs.push( `${pad}float inputs:clearcoat.connect = </Materials/Material_${material.id}/Texture_${material.clearcoatMap.id}_clearcoat.outputs:r>` );
|
672 |
+
samplers.push( buildTexture( material.clearcoatMap, 'clearcoat', new Color( material.clearcoat, material.clearcoat, material.clearcoat ) ) );
|
673 |
+
|
674 |
+
} else {
|
675 |
+
|
676 |
+
inputs.push( `${pad}float inputs:clearcoat = ${material.clearcoat}` );
|
677 |
+
|
678 |
+
}
|
679 |
+
|
680 |
+
if ( material.clearcoatRoughnessMap !== null ) {
|
681 |
+
|
682 |
+
inputs.push( `${pad}float inputs:clearcoatRoughness.connect = </Materials/Material_${material.id}/Texture_${material.clearcoatRoughnessMap.id}_clearcoatRoughness.outputs:g>` );
|
683 |
+
samplers.push( buildTexture( material.clearcoatRoughnessMap, 'clearcoatRoughness', new Color( material.clearcoatRoughness, material.clearcoatRoughness, material.clearcoatRoughness ) ) );
|
684 |
+
|
685 |
+
} else {
|
686 |
+
|
687 |
+
inputs.push( `${pad}float inputs:clearcoatRoughness = ${material.clearcoatRoughness}` );
|
688 |
+
|
689 |
+
}
|
690 |
+
|
691 |
+
inputs.push( `${ pad }float inputs:ior = ${ material.ior }` );
|
692 |
+
|
693 |
+
}
|
694 |
+
|
695 |
+
return `
|
696 |
+
def Material "Material_${ material.id }"
|
697 |
+
{
|
698 |
+
def Shader "PreviewSurface"
|
699 |
+
{
|
700 |
+
uniform token info:id = "UsdPreviewSurface"
|
701 |
+
${ inputs.join( '\n' ) }
|
702 |
+
int inputs:useSpecularWorkflow = 0
|
703 |
+
token outputs:surface
|
704 |
+
}
|
705 |
+
|
706 |
+
token outputs:surface.connect = </Materials/Material_${ material.id }/PreviewSurface.outputs:surface>
|
707 |
+
|
708 |
+
${ samplers.join( '\n' ) }
|
709 |
+
|
710 |
+
}
|
711 |
+
`;
|
712 |
+
|
713 |
+
}
|
714 |
+
|
715 |
+
function buildColor( color ) {
|
716 |
+
|
717 |
+
return `(${ color.r }, ${ color.g }, ${ color.b })`;
|
718 |
+
|
719 |
+
}
|
720 |
+
|
721 |
+
function buildColor4( color ) {
|
722 |
+
|
723 |
+
return `(${ color.r }, ${ color.g }, ${ color.b }, 1.0)`;
|
724 |
+
|
725 |
+
}
|
726 |
+
|
727 |
+
function buildVector2( vector ) {
|
728 |
+
|
729 |
+
return `(${ vector.x }, ${ vector.y })`;
|
730 |
+
|
731 |
+
}
|
732 |
+
|
733 |
+
|
734 |
+
function buildCamera( camera ) {
|
735 |
+
|
736 |
+
const name = camera.name ? camera.name : 'Camera_' + camera.id;
|
737 |
+
|
738 |
+
const transform = buildMatrix( camera.matrixWorld );
|
739 |
+
|
740 |
+
if ( camera.matrixWorld.determinant() < 0 ) {
|
741 |
+
|
742 |
+
console.warn( 'THREE.USDZExporter: USDZ does not support negative scales', camera );
|
743 |
+
|
744 |
+
}
|
745 |
+
|
746 |
+
if ( camera.isOrthographicCamera ) {
|
747 |
+
|
748 |
+
return `def Camera "${name}"
|
749 |
+
{
|
750 |
+
matrix4d xformOp:transform = ${ transform }
|
751 |
+
uniform token[] xformOpOrder = ["xformOp:transform"]
|
752 |
+
|
753 |
+
float2 clippingRange = (${ camera.near.toPrecision( PRECISION ) }, ${ camera.far.toPrecision( PRECISION ) })
|
754 |
+
float horizontalAperture = ${ ( ( Math.abs( camera.left ) + Math.abs( camera.right ) ) * 10 ).toPrecision( PRECISION ) }
|
755 |
+
float verticalAperture = ${ ( ( Math.abs( camera.top ) + Math.abs( camera.bottom ) ) * 10 ).toPrecision( PRECISION ) }
|
756 |
+
token projection = "orthographic"
|
757 |
+
}
|
758 |
+
|
759 |
+
`;
|
760 |
+
|
761 |
+
} else {
|
762 |
+
|
763 |
+
return `def Camera "${name}"
|
764 |
+
{
|
765 |
+
matrix4d xformOp:transform = ${ transform }
|
766 |
+
uniform token[] xformOpOrder = ["xformOp:transform"]
|
767 |
+
|
768 |
+
float2 clippingRange = (${ camera.near.toPrecision( PRECISION ) }, ${ camera.far.toPrecision( PRECISION ) })
|
769 |
+
float focalLength = ${ camera.getFocalLength().toPrecision( PRECISION ) }
|
770 |
+
float focusDistance = ${ camera.focus.toPrecision( PRECISION ) }
|
771 |
+
float horizontalAperture = ${ camera.getFilmWidth().toPrecision( PRECISION ) }
|
772 |
+
token projection = "perspective"
|
773 |
+
float verticalAperture = ${ camera.getFilmHeight().toPrecision( PRECISION ) }
|
774 |
+
}
|
775 |
+
|
776 |
+
`;
|
777 |
+
|
778 |
+
}
|
779 |
+
|
780 |
+
}
|
781 |
+
|
782 |
+
export { USDZExporter };
|
libs/three.js/0.172.0/jsm/geometries/BoxLineGeometry.js
ADDED
@@ -0,0 +1,69 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BufferGeometry,
|
3 |
+
Float32BufferAttribute
|
4 |
+
} from 'three';
|
5 |
+
|
6 |
+
class BoxLineGeometry extends BufferGeometry {
|
7 |
+
|
8 |
+
constructor( width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1 ) {
|
9 |
+
|
10 |
+
super();
|
11 |
+
|
12 |
+
widthSegments = Math.floor( widthSegments );
|
13 |
+
heightSegments = Math.floor( heightSegments );
|
14 |
+
depthSegments = Math.floor( depthSegments );
|
15 |
+
|
16 |
+
const widthHalf = width / 2;
|
17 |
+
const heightHalf = height / 2;
|
18 |
+
const depthHalf = depth / 2;
|
19 |
+
|
20 |
+
const segmentWidth = width / widthSegments;
|
21 |
+
const segmentHeight = height / heightSegments;
|
22 |
+
const segmentDepth = depth / depthSegments;
|
23 |
+
|
24 |
+
const vertices = [];
|
25 |
+
|
26 |
+
let x = - widthHalf;
|
27 |
+
let y = - heightHalf;
|
28 |
+
let z = - depthHalf;
|
29 |
+
|
30 |
+
for ( let i = 0; i <= widthSegments; i ++ ) {
|
31 |
+
|
32 |
+
vertices.push( x, - heightHalf, - depthHalf, x, heightHalf, - depthHalf );
|
33 |
+
vertices.push( x, heightHalf, - depthHalf, x, heightHalf, depthHalf );
|
34 |
+
vertices.push( x, heightHalf, depthHalf, x, - heightHalf, depthHalf );
|
35 |
+
vertices.push( x, - heightHalf, depthHalf, x, - heightHalf, - depthHalf );
|
36 |
+
|
37 |
+
x += segmentWidth;
|
38 |
+
|
39 |
+
}
|
40 |
+
|
41 |
+
for ( let i = 0; i <= heightSegments; i ++ ) {
|
42 |
+
|
43 |
+
vertices.push( - widthHalf, y, - depthHalf, widthHalf, y, - depthHalf );
|
44 |
+
vertices.push( widthHalf, y, - depthHalf, widthHalf, y, depthHalf );
|
45 |
+
vertices.push( widthHalf, y, depthHalf, - widthHalf, y, depthHalf );
|
46 |
+
vertices.push( - widthHalf, y, depthHalf, - widthHalf, y, - depthHalf );
|
47 |
+
|
48 |
+
y += segmentHeight;
|
49 |
+
|
50 |
+
}
|
51 |
+
|
52 |
+
for ( let i = 0; i <= depthSegments; i ++ ) {
|
53 |
+
|
54 |
+
vertices.push( - widthHalf, - heightHalf, z, - widthHalf, heightHalf, z );
|
55 |
+
vertices.push( - widthHalf, heightHalf, z, widthHalf, heightHalf, z );
|
56 |
+
vertices.push( widthHalf, heightHalf, z, widthHalf, - heightHalf, z );
|
57 |
+
vertices.push( widthHalf, - heightHalf, z, - widthHalf, - heightHalf, z );
|
58 |
+
|
59 |
+
z += segmentDepth;
|
60 |
+
|
61 |
+
}
|
62 |
+
|
63 |
+
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
|
64 |
+
|
65 |
+
}
|
66 |
+
|
67 |
+
}
|
68 |
+
|
69 |
+
export { BoxLineGeometry };
|
libs/three.js/0.172.0/jsm/geometries/ConvexGeometry.js
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BufferGeometry,
|
3 |
+
Float32BufferAttribute
|
4 |
+
} from 'three';
|
5 |
+
import { ConvexHull } from '../math/ConvexHull.js';
|
6 |
+
|
7 |
+
class ConvexGeometry extends BufferGeometry {
|
8 |
+
|
9 |
+
constructor( points = [] ) {
|
10 |
+
|
11 |
+
super();
|
12 |
+
|
13 |
+
// buffers
|
14 |
+
|
15 |
+
const vertices = [];
|
16 |
+
const normals = [];
|
17 |
+
|
18 |
+
const convexHull = new ConvexHull().setFromPoints( points );
|
19 |
+
|
20 |
+
// generate vertices and normals
|
21 |
+
|
22 |
+
const faces = convexHull.faces;
|
23 |
+
|
24 |
+
for ( let i = 0; i < faces.length; i ++ ) {
|
25 |
+
|
26 |
+
const face = faces[ i ];
|
27 |
+
let edge = face.edge;
|
28 |
+
|
29 |
+
// we move along a doubly-connected edge list to access all face points (see HalfEdge docs)
|
30 |
+
|
31 |
+
do {
|
32 |
+
|
33 |
+
const point = edge.head().point;
|
34 |
+
|
35 |
+
vertices.push( point.x, point.y, point.z );
|
36 |
+
normals.push( face.normal.x, face.normal.y, face.normal.z );
|
37 |
+
|
38 |
+
edge = edge.next;
|
39 |
+
|
40 |
+
} while ( edge !== face.edge );
|
41 |
+
|
42 |
+
}
|
43 |
+
|
44 |
+
// build geometry
|
45 |
+
|
46 |
+
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
|
47 |
+
this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
|
48 |
+
|
49 |
+
}
|
50 |
+
|
51 |
+
}
|
52 |
+
|
53 |
+
export { ConvexGeometry };
|
libs/three.js/0.172.0/jsm/geometries/DecalGeometry.js
ADDED
@@ -0,0 +1,408 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BufferGeometry,
|
3 |
+
Euler,
|
4 |
+
Float32BufferAttribute,
|
5 |
+
Matrix3,
|
6 |
+
Matrix4,
|
7 |
+
Mesh,
|
8 |
+
Vector3
|
9 |
+
} from 'three';
|
10 |
+
|
11 |
+
/**
|
12 |
+
* You can use this geometry to create a decal mesh, that serves different kinds of purposes.
|
13 |
+
* e.g. adding unique details to models, performing dynamic visual environmental changes or covering seams.
|
14 |
+
*
|
15 |
+
* Constructor parameter:
|
16 |
+
*
|
17 |
+
* mesh — Any mesh object
|
18 |
+
* position — Position of the decal projector
|
19 |
+
* orientation — Orientation of the decal projector
|
20 |
+
* size — Size of the decal projector
|
21 |
+
*
|
22 |
+
* reference: http://blog.wolfire.com/2009/06/how-to-project-decals/
|
23 |
+
*
|
24 |
+
*/
|
25 |
+
|
26 |
+
class DecalGeometry extends BufferGeometry {
|
27 |
+
|
28 |
+
constructor( mesh = new Mesh(), position = new Vector3(), orientation = new Euler(), size = new Vector3( 1, 1, 1 ) ) {
|
29 |
+
|
30 |
+
super();
|
31 |
+
|
32 |
+
// buffers
|
33 |
+
|
34 |
+
const vertices = [];
|
35 |
+
const normals = [];
|
36 |
+
const uvs = [];
|
37 |
+
|
38 |
+
// helpers
|
39 |
+
|
40 |
+
const plane = new Vector3();
|
41 |
+
|
42 |
+
const normalMatrix = new Matrix3().getNormalMatrix( mesh.matrixWorld );
|
43 |
+
|
44 |
+
// this matrix represents the transformation of the decal projector
|
45 |
+
|
46 |
+
const projectorMatrix = new Matrix4();
|
47 |
+
projectorMatrix.makeRotationFromEuler( orientation );
|
48 |
+
projectorMatrix.setPosition( position );
|
49 |
+
|
50 |
+
const projectorMatrixInverse = new Matrix4();
|
51 |
+
projectorMatrixInverse.copy( projectorMatrix ).invert();
|
52 |
+
|
53 |
+
// generate buffers
|
54 |
+
|
55 |
+
generate();
|
56 |
+
|
57 |
+
// build geometry
|
58 |
+
|
59 |
+
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
|
60 |
+
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
|
61 |
+
|
62 |
+
if ( normals.length > 0 ) {
|
63 |
+
|
64 |
+
this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
|
65 |
+
|
66 |
+
}
|
67 |
+
|
68 |
+
//
|
69 |
+
|
70 |
+
function generate() {
|
71 |
+
|
72 |
+
let decalVertices = [];
|
73 |
+
|
74 |
+
const vertex = new Vector3();
|
75 |
+
const normal = new Vector3();
|
76 |
+
|
77 |
+
// handle different geometry types
|
78 |
+
|
79 |
+
const geometry = mesh.geometry;
|
80 |
+
|
81 |
+
const positionAttribute = geometry.attributes.position;
|
82 |
+
const normalAttribute = geometry.attributes.normal;
|
83 |
+
|
84 |
+
// first, create an array of 'DecalVertex' objects
|
85 |
+
// three consecutive 'DecalVertex' objects represent a single face
|
86 |
+
//
|
87 |
+
// this data structure will be later used to perform the clipping
|
88 |
+
|
89 |
+
if ( geometry.index !== null ) {
|
90 |
+
|
91 |
+
// indexed BufferGeometry
|
92 |
+
|
93 |
+
const index = geometry.index;
|
94 |
+
|
95 |
+
for ( let i = 0; i < index.count; i ++ ) {
|
96 |
+
|
97 |
+
vertex.fromBufferAttribute( positionAttribute, index.getX( i ) );
|
98 |
+
|
99 |
+
if ( normalAttribute ) {
|
100 |
+
|
101 |
+
normal.fromBufferAttribute( normalAttribute, index.getX( i ) );
|
102 |
+
pushDecalVertex( decalVertices, vertex, normal );
|
103 |
+
|
104 |
+
} else {
|
105 |
+
|
106 |
+
pushDecalVertex( decalVertices, vertex );
|
107 |
+
|
108 |
+
}
|
109 |
+
|
110 |
+
}
|
111 |
+
|
112 |
+
} else {
|
113 |
+
|
114 |
+
if ( positionAttribute === undefined ) return; // empty geometry
|
115 |
+
|
116 |
+
// non-indexed BufferGeometry
|
117 |
+
|
118 |
+
for ( let i = 0; i < positionAttribute.count; i ++ ) {
|
119 |
+
|
120 |
+
vertex.fromBufferAttribute( positionAttribute, i );
|
121 |
+
|
122 |
+
if ( normalAttribute ) {
|
123 |
+
|
124 |
+
normal.fromBufferAttribute( normalAttribute, i );
|
125 |
+
pushDecalVertex( decalVertices, vertex, normal );
|
126 |
+
|
127 |
+
} else {
|
128 |
+
|
129 |
+
pushDecalVertex( decalVertices, vertex );
|
130 |
+
|
131 |
+
}
|
132 |
+
|
133 |
+
}
|
134 |
+
|
135 |
+
}
|
136 |
+
|
137 |
+
// second, clip the geometry so that it doesn't extend out from the projector
|
138 |
+
|
139 |
+
decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) );
|
140 |
+
decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) );
|
141 |
+
decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) );
|
142 |
+
decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) );
|
143 |
+
decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) );
|
144 |
+
decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) );
|
145 |
+
|
146 |
+
// third, generate final vertices, normals and uvs
|
147 |
+
|
148 |
+
for ( let i = 0; i < decalVertices.length; i ++ ) {
|
149 |
+
|
150 |
+
const decalVertex = decalVertices[ i ];
|
151 |
+
|
152 |
+
// create texture coordinates (we are still in projector space)
|
153 |
+
|
154 |
+
uvs.push(
|
155 |
+
0.5 + ( decalVertex.position.x / size.x ),
|
156 |
+
0.5 + ( decalVertex.position.y / size.y )
|
157 |
+
);
|
158 |
+
|
159 |
+
// transform the vertex back to world space
|
160 |
+
|
161 |
+
decalVertex.position.applyMatrix4( projectorMatrix );
|
162 |
+
|
163 |
+
// now create vertex and normal buffer data
|
164 |
+
|
165 |
+
vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z );
|
166 |
+
|
167 |
+
if ( decalVertex.normal !== null ) {
|
168 |
+
|
169 |
+
normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z );
|
170 |
+
|
171 |
+
}
|
172 |
+
|
173 |
+
}
|
174 |
+
|
175 |
+
}
|
176 |
+
|
177 |
+
function pushDecalVertex( decalVertices, vertex, normal = null ) {
|
178 |
+
|
179 |
+
// transform the vertex to world space, then to projector space
|
180 |
+
|
181 |
+
vertex.applyMatrix4( mesh.matrixWorld );
|
182 |
+
vertex.applyMatrix4( projectorMatrixInverse );
|
183 |
+
|
184 |
+
if ( normal ) {
|
185 |
+
|
186 |
+
normal.applyNormalMatrix( normalMatrix );
|
187 |
+
decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) );
|
188 |
+
|
189 |
+
} else {
|
190 |
+
|
191 |
+
decalVertices.push( new DecalVertex( vertex.clone() ) );
|
192 |
+
|
193 |
+
}
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
function clipGeometry( inVertices, plane ) {
|
198 |
+
|
199 |
+
const outVertices = [];
|
200 |
+
|
201 |
+
const s = 0.5 * Math.abs( size.dot( plane ) );
|
202 |
+
|
203 |
+
// a single iteration clips one face,
|
204 |
+
// which consists of three consecutive 'DecalVertex' objects
|
205 |
+
|
206 |
+
for ( let i = 0; i < inVertices.length; i += 3 ) {
|
207 |
+
|
208 |
+
let total = 0;
|
209 |
+
let nV1;
|
210 |
+
let nV2;
|
211 |
+
let nV3;
|
212 |
+
let nV4;
|
213 |
+
|
214 |
+
const d1 = inVertices[ i + 0 ].position.dot( plane ) - s;
|
215 |
+
const d2 = inVertices[ i + 1 ].position.dot( plane ) - s;
|
216 |
+
const d3 = inVertices[ i + 2 ].position.dot( plane ) - s;
|
217 |
+
|
218 |
+
const v1Out = d1 > 0;
|
219 |
+
const v2Out = d2 > 0;
|
220 |
+
const v3Out = d3 > 0;
|
221 |
+
|
222 |
+
// calculate, how many vertices of the face lie outside of the clipping plane
|
223 |
+
|
224 |
+
total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 );
|
225 |
+
|
226 |
+
switch ( total ) {
|
227 |
+
|
228 |
+
case 0: {
|
229 |
+
|
230 |
+
// the entire face lies inside of the plane, no clipping needed
|
231 |
+
|
232 |
+
outVertices.push( inVertices[ i ] );
|
233 |
+
outVertices.push( inVertices[ i + 1 ] );
|
234 |
+
outVertices.push( inVertices[ i + 2 ] );
|
235 |
+
break;
|
236 |
+
|
237 |
+
}
|
238 |
+
|
239 |
+
case 1: {
|
240 |
+
|
241 |
+
// one vertex lies outside of the plane, perform clipping
|
242 |
+
|
243 |
+
if ( v1Out ) {
|
244 |
+
|
245 |
+
nV1 = inVertices[ i + 1 ];
|
246 |
+
nV2 = inVertices[ i + 2 ];
|
247 |
+
nV3 = clip( inVertices[ i ], nV1, plane, s );
|
248 |
+
nV4 = clip( inVertices[ i ], nV2, plane, s );
|
249 |
+
|
250 |
+
}
|
251 |
+
|
252 |
+
if ( v2Out ) {
|
253 |
+
|
254 |
+
nV1 = inVertices[ i ];
|
255 |
+
nV2 = inVertices[ i + 2 ];
|
256 |
+
nV3 = clip( inVertices[ i + 1 ], nV1, plane, s );
|
257 |
+
nV4 = clip( inVertices[ i + 1 ], nV2, plane, s );
|
258 |
+
|
259 |
+
outVertices.push( nV3 );
|
260 |
+
outVertices.push( nV2.clone() );
|
261 |
+
outVertices.push( nV1.clone() );
|
262 |
+
|
263 |
+
outVertices.push( nV2.clone() );
|
264 |
+
outVertices.push( nV3.clone() );
|
265 |
+
outVertices.push( nV4 );
|
266 |
+
break;
|
267 |
+
|
268 |
+
}
|
269 |
+
|
270 |
+
if ( v3Out ) {
|
271 |
+
|
272 |
+
nV1 = inVertices[ i ];
|
273 |
+
nV2 = inVertices[ i + 1 ];
|
274 |
+
nV3 = clip( inVertices[ i + 2 ], nV1, plane, s );
|
275 |
+
nV4 = clip( inVertices[ i + 2 ], nV2, plane, s );
|
276 |
+
|
277 |
+
}
|
278 |
+
|
279 |
+
outVertices.push( nV1.clone() );
|
280 |
+
outVertices.push( nV2.clone() );
|
281 |
+
outVertices.push( nV3 );
|
282 |
+
|
283 |
+
outVertices.push( nV4 );
|
284 |
+
outVertices.push( nV3.clone() );
|
285 |
+
outVertices.push( nV2.clone() );
|
286 |
+
|
287 |
+
break;
|
288 |
+
|
289 |
+
}
|
290 |
+
|
291 |
+
case 2: {
|
292 |
+
|
293 |
+
// two vertices lies outside of the plane, perform clipping
|
294 |
+
|
295 |
+
if ( ! v1Out ) {
|
296 |
+
|
297 |
+
nV1 = inVertices[ i ].clone();
|
298 |
+
nV2 = clip( nV1, inVertices[ i + 1 ], plane, s );
|
299 |
+
nV3 = clip( nV1, inVertices[ i + 2 ], plane, s );
|
300 |
+
outVertices.push( nV1 );
|
301 |
+
outVertices.push( nV2 );
|
302 |
+
outVertices.push( nV3 );
|
303 |
+
|
304 |
+
}
|
305 |
+
|
306 |
+
if ( ! v2Out ) {
|
307 |
+
|
308 |
+
nV1 = inVertices[ i + 1 ].clone();
|
309 |
+
nV2 = clip( nV1, inVertices[ i + 2 ], plane, s );
|
310 |
+
nV3 = clip( nV1, inVertices[ i ], plane, s );
|
311 |
+
outVertices.push( nV1 );
|
312 |
+
outVertices.push( nV2 );
|
313 |
+
outVertices.push( nV3 );
|
314 |
+
|
315 |
+
}
|
316 |
+
|
317 |
+
if ( ! v3Out ) {
|
318 |
+
|
319 |
+
nV1 = inVertices[ i + 2 ].clone();
|
320 |
+
nV2 = clip( nV1, inVertices[ i ], plane, s );
|
321 |
+
nV3 = clip( nV1, inVertices[ i + 1 ], plane, s );
|
322 |
+
outVertices.push( nV1 );
|
323 |
+
outVertices.push( nV2 );
|
324 |
+
outVertices.push( nV3 );
|
325 |
+
|
326 |
+
}
|
327 |
+
|
328 |
+
break;
|
329 |
+
|
330 |
+
}
|
331 |
+
|
332 |
+
case 3: {
|
333 |
+
|
334 |
+
// the entire face lies outside of the plane, so let's discard the corresponding vertices
|
335 |
+
|
336 |
+
break;
|
337 |
+
|
338 |
+
}
|
339 |
+
|
340 |
+
}
|
341 |
+
|
342 |
+
}
|
343 |
+
|
344 |
+
return outVertices;
|
345 |
+
|
346 |
+
}
|
347 |
+
|
348 |
+
function clip( v0, v1, p, s ) {
|
349 |
+
|
350 |
+
const d0 = v0.position.dot( p ) - s;
|
351 |
+
const d1 = v1.position.dot( p ) - s;
|
352 |
+
|
353 |
+
const s0 = d0 / ( d0 - d1 );
|
354 |
+
|
355 |
+
const position = new Vector3(
|
356 |
+
v0.position.x + s0 * ( v1.position.x - v0.position.x ),
|
357 |
+
v0.position.y + s0 * ( v1.position.y - v0.position.y ),
|
358 |
+
v0.position.z + s0 * ( v1.position.z - v0.position.z )
|
359 |
+
);
|
360 |
+
|
361 |
+
let normal = null;
|
362 |
+
|
363 |
+
if ( v0.normal !== null && v1.normal !== null ) {
|
364 |
+
|
365 |
+
normal = new Vector3(
|
366 |
+
v0.normal.x + s0 * ( v1.normal.x - v0.normal.x ),
|
367 |
+
v0.normal.y + s0 * ( v1.normal.y - v0.normal.y ),
|
368 |
+
v0.normal.z + s0 * ( v1.normal.z - v0.normal.z )
|
369 |
+
);
|
370 |
+
|
371 |
+
}
|
372 |
+
|
373 |
+
const v = new DecalVertex( position, normal );
|
374 |
+
|
375 |
+
// need to clip more values (texture coordinates)? do it this way:
|
376 |
+
// intersectpoint.value = a.value + s * ( b.value - a.value );
|
377 |
+
|
378 |
+
return v;
|
379 |
+
|
380 |
+
}
|
381 |
+
|
382 |
+
}
|
383 |
+
|
384 |
+
}
|
385 |
+
|
386 |
+
// helper
|
387 |
+
|
388 |
+
class DecalVertex {
|
389 |
+
|
390 |
+
constructor( position, normal = null ) {
|
391 |
+
|
392 |
+
this.position = position;
|
393 |
+
this.normal = normal;
|
394 |
+
|
395 |
+
}
|
396 |
+
|
397 |
+
clone() {
|
398 |
+
|
399 |
+
const position = this.position.clone();
|
400 |
+
const normal = ( this.normal !== null ) ? this.normal.clone() : null;
|
401 |
+
|
402 |
+
return new this.constructor( position, normal );
|
403 |
+
|
404 |
+
}
|
405 |
+
|
406 |
+
}
|
407 |
+
|
408 |
+
export { DecalGeometry, DecalVertex };
|
libs/three.js/0.172.0/jsm/geometries/ParametricGeometries.js
ADDED
@@ -0,0 +1,254 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
Curve,
|
3 |
+
Vector3
|
4 |
+
} from 'three';
|
5 |
+
|
6 |
+
import { ParametricGeometry } from './ParametricGeometry.js';
|
7 |
+
|
8 |
+
/**
|
9 |
+
* Experimenting of primitive geometry creation using Surface Parametric equations
|
10 |
+
*/
|
11 |
+
|
12 |
+
const ParametricGeometries = {
|
13 |
+
|
14 |
+
klein: function ( v, u, target ) {
|
15 |
+
|
16 |
+
u *= Math.PI;
|
17 |
+
v *= 2 * Math.PI;
|
18 |
+
|
19 |
+
u = u * 2;
|
20 |
+
let x, z;
|
21 |
+
if ( u < Math.PI ) {
|
22 |
+
|
23 |
+
x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v );
|
24 |
+
z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v );
|
25 |
+
|
26 |
+
} else {
|
27 |
+
|
28 |
+
x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI );
|
29 |
+
z = - 8 * Math.sin( u );
|
30 |
+
|
31 |
+
}
|
32 |
+
|
33 |
+
const y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v );
|
34 |
+
|
35 |
+
target.set( x, y, z );
|
36 |
+
|
37 |
+
},
|
38 |
+
|
39 |
+
plane: function ( width, height ) {
|
40 |
+
|
41 |
+
return function ( u, v, target ) {
|
42 |
+
|
43 |
+
const x = u * width;
|
44 |
+
const y = 0;
|
45 |
+
const z = v * height;
|
46 |
+
|
47 |
+
target.set( x, y, z );
|
48 |
+
|
49 |
+
};
|
50 |
+
|
51 |
+
},
|
52 |
+
|
53 |
+
mobius: function ( u, t, target ) {
|
54 |
+
|
55 |
+
// flat mobius strip
|
56 |
+
// http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations-
|
57 |
+
u = u - 0.5;
|
58 |
+
const v = 2 * Math.PI * t;
|
59 |
+
|
60 |
+
const a = 2;
|
61 |
+
|
62 |
+
const x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) );
|
63 |
+
const y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) );
|
64 |
+
const z = u * Math.sin( v / 2 );
|
65 |
+
|
66 |
+
target.set( x, y, z );
|
67 |
+
|
68 |
+
},
|
69 |
+
|
70 |
+
mobius3d: function ( u, t, target ) {
|
71 |
+
|
72 |
+
// volumetric mobius strip
|
73 |
+
|
74 |
+
u *= Math.PI;
|
75 |
+
t *= 2 * Math.PI;
|
76 |
+
|
77 |
+
u = u * 2;
|
78 |
+
const phi = u / 2;
|
79 |
+
const major = 2.25, a = 0.125, b = 0.65;
|
80 |
+
|
81 |
+
let x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi );
|
82 |
+
const z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi );
|
83 |
+
const y = ( major + x ) * Math.sin( u );
|
84 |
+
x = ( major + x ) * Math.cos( u );
|
85 |
+
|
86 |
+
target.set( x, y, z );
|
87 |
+
|
88 |
+
}
|
89 |
+
|
90 |
+
};
|
91 |
+
|
92 |
+
|
93 |
+
/*********************************************
|
94 |
+
*
|
95 |
+
* Parametric Replacement for TubeGeometry
|
96 |
+
*
|
97 |
+
*********************************************/
|
98 |
+
|
99 |
+
ParametricGeometries.TubeGeometry = class TubeGeometry extends ParametricGeometry {
|
100 |
+
|
101 |
+
constructor( path, segments = 64, radius = 1, segmentsRadius = 8, closed = false ) {
|
102 |
+
|
103 |
+
const numpoints = segments + 1;
|
104 |
+
|
105 |
+
const frames = path.computeFrenetFrames( segments, closed ),
|
106 |
+
tangents = frames.tangents,
|
107 |
+
normals = frames.normals,
|
108 |
+
binormals = frames.binormals;
|
109 |
+
|
110 |
+
const position = new Vector3();
|
111 |
+
|
112 |
+
function ParametricTube( u, v, target ) {
|
113 |
+
|
114 |
+
v *= 2 * Math.PI;
|
115 |
+
|
116 |
+
const i = Math.floor( u * ( numpoints - 1 ) );
|
117 |
+
|
118 |
+
path.getPointAt( u, position );
|
119 |
+
|
120 |
+
const normal = normals[ i ];
|
121 |
+
const binormal = binormals[ i ];
|
122 |
+
|
123 |
+
const cx = - radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
|
124 |
+
const cy = radius * Math.sin( v );
|
125 |
+
|
126 |
+
position.x += cx * normal.x + cy * binormal.x;
|
127 |
+
position.y += cx * normal.y + cy * binormal.y;
|
128 |
+
position.z += cx * normal.z + cy * binormal.z;
|
129 |
+
|
130 |
+
target.copy( position );
|
131 |
+
|
132 |
+
}
|
133 |
+
|
134 |
+
super( ParametricTube, segments, segmentsRadius );
|
135 |
+
|
136 |
+
// proxy internals
|
137 |
+
|
138 |
+
this.tangents = tangents;
|
139 |
+
this.normals = normals;
|
140 |
+
this.binormals = binormals;
|
141 |
+
|
142 |
+
this.path = path;
|
143 |
+
this.segments = segments;
|
144 |
+
this.radius = radius;
|
145 |
+
this.segmentsRadius = segmentsRadius;
|
146 |
+
this.closed = closed;
|
147 |
+
|
148 |
+
}
|
149 |
+
|
150 |
+
};
|
151 |
+
|
152 |
+
|
153 |
+
/*********************************************
|
154 |
+
*
|
155 |
+
* Parametric Replacement for TorusKnotGeometry
|
156 |
+
*
|
157 |
+
*********************************************/
|
158 |
+
ParametricGeometries.TorusKnotGeometry = class TorusKnotGeometry extends ParametricGeometries.TubeGeometry {
|
159 |
+
|
160 |
+
constructor( radius = 200, tube = 40, segmentsT = 64, segmentsR = 8, p = 2, q = 3 ) {
|
161 |
+
|
162 |
+
class TorusKnotCurve extends Curve {
|
163 |
+
|
164 |
+
getPoint( t, optionalTarget = new Vector3() ) {
|
165 |
+
|
166 |
+
const point = optionalTarget;
|
167 |
+
|
168 |
+
t *= Math.PI * 2;
|
169 |
+
|
170 |
+
const r = 0.5;
|
171 |
+
|
172 |
+
const x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t );
|
173 |
+
const y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t );
|
174 |
+
const z = r * Math.sin( q * t );
|
175 |
+
|
176 |
+
return point.set( x, y, z ).multiplyScalar( radius );
|
177 |
+
|
178 |
+
}
|
179 |
+
|
180 |
+
}
|
181 |
+
|
182 |
+
const segments = segmentsT;
|
183 |
+
const radiusSegments = segmentsR;
|
184 |
+
const extrudePath = new TorusKnotCurve();
|
185 |
+
|
186 |
+
super( extrudePath, segments, tube, radiusSegments, true, false );
|
187 |
+
|
188 |
+
this.radius = radius;
|
189 |
+
this.tube = tube;
|
190 |
+
this.segmentsT = segmentsT;
|
191 |
+
this.segmentsR = segmentsR;
|
192 |
+
this.p = p;
|
193 |
+
this.q = q;
|
194 |
+
|
195 |
+
}
|
196 |
+
|
197 |
+
};
|
198 |
+
|
199 |
+
/*********************************************
|
200 |
+
*
|
201 |
+
* Parametric Replacement for SphereGeometry
|
202 |
+
*
|
203 |
+
*********************************************/
|
204 |
+
ParametricGeometries.SphereGeometry = class SphereGeometry extends ParametricGeometry {
|
205 |
+
|
206 |
+
constructor( size, u, v ) {
|
207 |
+
|
208 |
+
function sphere( u, v, target ) {
|
209 |
+
|
210 |
+
u *= Math.PI;
|
211 |
+
v *= 2 * Math.PI;
|
212 |
+
|
213 |
+
const x = size * Math.sin( u ) * Math.cos( v );
|
214 |
+
const y = size * Math.sin( u ) * Math.sin( v );
|
215 |
+
const z = size * Math.cos( u );
|
216 |
+
|
217 |
+
target.set( x, y, z );
|
218 |
+
|
219 |
+
}
|
220 |
+
|
221 |
+
super( sphere, u, v );
|
222 |
+
|
223 |
+
}
|
224 |
+
|
225 |
+
};
|
226 |
+
|
227 |
+
|
228 |
+
/*********************************************
|
229 |
+
*
|
230 |
+
* Parametric Replacement for PlaneGeometry
|
231 |
+
*
|
232 |
+
*********************************************/
|
233 |
+
|
234 |
+
ParametricGeometries.PlaneGeometry = class PlaneGeometry extends ParametricGeometry {
|
235 |
+
|
236 |
+
constructor( width, depth, segmentsWidth, segmentsDepth ) {
|
237 |
+
|
238 |
+
function plane( u, v, target ) {
|
239 |
+
|
240 |
+
const x = u * width;
|
241 |
+
const y = 0;
|
242 |
+
const z = v * depth;
|
243 |
+
|
244 |
+
target.set( x, y, z );
|
245 |
+
|
246 |
+
}
|
247 |
+
|
248 |
+
super( plane, segmentsWidth, segmentsDepth );
|
249 |
+
|
250 |
+
}
|
251 |
+
|
252 |
+
};
|
253 |
+
|
254 |
+
export { ParametricGeometries };
|
libs/three.js/0.172.0/jsm/geometries/ParametricGeometry.js
ADDED
@@ -0,0 +1,139 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Parametric Surfaces Geometry
|
3 |
+
* based on the brilliant article by @prideout https://prideout.net/blog/old/blog/index.html@p=44.html
|
4 |
+
*/
|
5 |
+
|
6 |
+
import {
|
7 |
+
BufferGeometry,
|
8 |
+
Float32BufferAttribute,
|
9 |
+
Vector3
|
10 |
+
} from 'three';
|
11 |
+
|
12 |
+
class ParametricGeometry extends BufferGeometry {
|
13 |
+
|
14 |
+
constructor( func = ( u, v, target ) => target.set( u, v, Math.cos( u ) * Math.sin( v ) ), slices = 8, stacks = 8 ) {
|
15 |
+
|
16 |
+
super();
|
17 |
+
|
18 |
+
this.type = 'ParametricGeometry';
|
19 |
+
|
20 |
+
this.parameters = {
|
21 |
+
func: func,
|
22 |
+
slices: slices,
|
23 |
+
stacks: stacks
|
24 |
+
};
|
25 |
+
|
26 |
+
// buffers
|
27 |
+
|
28 |
+
const indices = [];
|
29 |
+
const vertices = [];
|
30 |
+
const normals = [];
|
31 |
+
const uvs = [];
|
32 |
+
|
33 |
+
const EPS = 0.00001;
|
34 |
+
|
35 |
+
const normal = new Vector3();
|
36 |
+
|
37 |
+
const p0 = new Vector3(), p1 = new Vector3();
|
38 |
+
const pu = new Vector3(), pv = new Vector3();
|
39 |
+
|
40 |
+
// generate vertices, normals and uvs
|
41 |
+
|
42 |
+
const sliceCount = slices + 1;
|
43 |
+
|
44 |
+
for ( let i = 0; i <= stacks; i ++ ) {
|
45 |
+
|
46 |
+
const v = i / stacks;
|
47 |
+
|
48 |
+
for ( let j = 0; j <= slices; j ++ ) {
|
49 |
+
|
50 |
+
const u = j / slices;
|
51 |
+
|
52 |
+
// vertex
|
53 |
+
|
54 |
+
func( u, v, p0 );
|
55 |
+
vertices.push( p0.x, p0.y, p0.z );
|
56 |
+
|
57 |
+
// normal
|
58 |
+
|
59 |
+
// approximate tangent vectors via finite differences
|
60 |
+
|
61 |
+
if ( u - EPS >= 0 ) {
|
62 |
+
|
63 |
+
func( u - EPS, v, p1 );
|
64 |
+
pu.subVectors( p0, p1 );
|
65 |
+
|
66 |
+
} else {
|
67 |
+
|
68 |
+
func( u + EPS, v, p1 );
|
69 |
+
pu.subVectors( p1, p0 );
|
70 |
+
|
71 |
+
}
|
72 |
+
|
73 |
+
if ( v - EPS >= 0 ) {
|
74 |
+
|
75 |
+
func( u, v - EPS, p1 );
|
76 |
+
pv.subVectors( p0, p1 );
|
77 |
+
|
78 |
+
} else {
|
79 |
+
|
80 |
+
func( u, v + EPS, p1 );
|
81 |
+
pv.subVectors( p1, p0 );
|
82 |
+
|
83 |
+
}
|
84 |
+
|
85 |
+
// cross product of tangent vectors returns surface normal
|
86 |
+
|
87 |
+
normal.crossVectors( pu, pv ).normalize();
|
88 |
+
normals.push( normal.x, normal.y, normal.z );
|
89 |
+
|
90 |
+
// uv
|
91 |
+
|
92 |
+
uvs.push( u, v );
|
93 |
+
|
94 |
+
}
|
95 |
+
|
96 |
+
}
|
97 |
+
|
98 |
+
// generate indices
|
99 |
+
|
100 |
+
for ( let i = 0; i < stacks; i ++ ) {
|
101 |
+
|
102 |
+
for ( let j = 0; j < slices; j ++ ) {
|
103 |
+
|
104 |
+
const a = i * sliceCount + j;
|
105 |
+
const b = i * sliceCount + j + 1;
|
106 |
+
const c = ( i + 1 ) * sliceCount + j + 1;
|
107 |
+
const d = ( i + 1 ) * sliceCount + j;
|
108 |
+
|
109 |
+
// faces one and two
|
110 |
+
|
111 |
+
indices.push( a, b, d );
|
112 |
+
indices.push( b, c, d );
|
113 |
+
|
114 |
+
}
|
115 |
+
|
116 |
+
}
|
117 |
+
|
118 |
+
// build geometry
|
119 |
+
|
120 |
+
this.setIndex( indices );
|
121 |
+
this.setAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
|
122 |
+
this.setAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
|
123 |
+
this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
|
124 |
+
|
125 |
+
}
|
126 |
+
|
127 |
+
copy( source ) {
|
128 |
+
|
129 |
+
super.copy( source );
|
130 |
+
|
131 |
+
this.parameters = Object.assign( {}, source.parameters );
|
132 |
+
|
133 |
+
return this;
|
134 |
+
|
135 |
+
}
|
136 |
+
|
137 |
+
}
|
138 |
+
|
139 |
+
export { ParametricGeometry };
|
libs/three.js/0.172.0/jsm/geometries/RoundedBoxGeometry.js
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BoxGeometry,
|
3 |
+
Vector3
|
4 |
+
} from 'three';
|
5 |
+
|
6 |
+
const _tempNormal = new Vector3();
|
7 |
+
|
8 |
+
function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength ) {
|
9 |
+
|
10 |
+
const totArcLength = 2 * Math.PI * radius / 4;
|
11 |
+
|
12 |
+
// length of the planes between the arcs on each axis
|
13 |
+
const centerLength = Math.max( sideLength - 2 * radius, 0 );
|
14 |
+
const halfArc = Math.PI / 4;
|
15 |
+
|
16 |
+
// Get the vector projected onto the Y plane
|
17 |
+
_tempNormal.copy( normal );
|
18 |
+
_tempNormal[ projectionAxis ] = 0;
|
19 |
+
_tempNormal.normalize();
|
20 |
+
|
21 |
+
// total amount of UV space alloted to a single arc
|
22 |
+
const arcUvRatio = 0.5 * totArcLength / ( totArcLength + centerLength );
|
23 |
+
|
24 |
+
// the distance along one arc the point is at
|
25 |
+
const arcAngleRatio = 1.0 - ( _tempNormal.angleTo( faceDirVector ) / halfArc );
|
26 |
+
|
27 |
+
if ( Math.sign( _tempNormal[ uvAxis ] ) === 1 ) {
|
28 |
+
|
29 |
+
return arcAngleRatio * arcUvRatio;
|
30 |
+
|
31 |
+
} else {
|
32 |
+
|
33 |
+
// total amount of UV space alloted to the plane between the arcs
|
34 |
+
const lenUv = centerLength / ( totArcLength + centerLength );
|
35 |
+
return lenUv + arcUvRatio + arcUvRatio * ( 1.0 - arcAngleRatio );
|
36 |
+
|
37 |
+
}
|
38 |
+
|
39 |
+
}
|
40 |
+
|
41 |
+
class RoundedBoxGeometry extends BoxGeometry {
|
42 |
+
|
43 |
+
constructor( width = 1, height = 1, depth = 1, segments = 2, radius = 0.1 ) {
|
44 |
+
|
45 |
+
// ensure segments is odd so we have a plane connecting the rounded corners
|
46 |
+
segments = segments * 2 + 1;
|
47 |
+
|
48 |
+
// ensure radius isn't bigger than shortest side
|
49 |
+
radius = Math.min( width / 2, height / 2, depth / 2, radius );
|
50 |
+
|
51 |
+
super( 1, 1, 1, segments, segments, segments );
|
52 |
+
|
53 |
+
// if we just have one segment we're the same as a regular box
|
54 |
+
if ( segments === 1 ) return;
|
55 |
+
|
56 |
+
const geometry2 = this.toNonIndexed();
|
57 |
+
|
58 |
+
this.index = null;
|
59 |
+
this.attributes.position = geometry2.attributes.position;
|
60 |
+
this.attributes.normal = geometry2.attributes.normal;
|
61 |
+
this.attributes.uv = geometry2.attributes.uv;
|
62 |
+
|
63 |
+
//
|
64 |
+
|
65 |
+
const position = new Vector3();
|
66 |
+
const normal = new Vector3();
|
67 |
+
|
68 |
+
const box = new Vector3( width, height, depth ).divideScalar( 2 ).subScalar( radius );
|
69 |
+
|
70 |
+
const positions = this.attributes.position.array;
|
71 |
+
const normals = this.attributes.normal.array;
|
72 |
+
const uvs = this.attributes.uv.array;
|
73 |
+
|
74 |
+
const faceTris = positions.length / 6;
|
75 |
+
const faceDirVector = new Vector3();
|
76 |
+
const halfSegmentSize = 0.5 / segments;
|
77 |
+
|
78 |
+
for ( let i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {
|
79 |
+
|
80 |
+
position.fromArray( positions, i );
|
81 |
+
normal.copy( position );
|
82 |
+
normal.x -= Math.sign( normal.x ) * halfSegmentSize;
|
83 |
+
normal.y -= Math.sign( normal.y ) * halfSegmentSize;
|
84 |
+
normal.z -= Math.sign( normal.z ) * halfSegmentSize;
|
85 |
+
normal.normalize();
|
86 |
+
|
87 |
+
positions[ i + 0 ] = box.x * Math.sign( position.x ) + normal.x * radius;
|
88 |
+
positions[ i + 1 ] = box.y * Math.sign( position.y ) + normal.y * radius;
|
89 |
+
positions[ i + 2 ] = box.z * Math.sign( position.z ) + normal.z * radius;
|
90 |
+
|
91 |
+
normals[ i + 0 ] = normal.x;
|
92 |
+
normals[ i + 1 ] = normal.y;
|
93 |
+
normals[ i + 2 ] = normal.z;
|
94 |
+
|
95 |
+
const side = Math.floor( i / faceTris );
|
96 |
+
|
97 |
+
switch ( side ) {
|
98 |
+
|
99 |
+
case 0: // right
|
100 |
+
|
101 |
+
// generate UVs along Z then Y
|
102 |
+
faceDirVector.set( 1, 0, 0 );
|
103 |
+
uvs[ j + 0 ] = getUv( faceDirVector, normal, 'z', 'y', radius, depth );
|
104 |
+
uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height );
|
105 |
+
break;
|
106 |
+
|
107 |
+
case 1: // left
|
108 |
+
|
109 |
+
// generate UVs along Z then Y
|
110 |
+
faceDirVector.set( - 1, 0, 0 );
|
111 |
+
uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'y', radius, depth );
|
112 |
+
uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height );
|
113 |
+
break;
|
114 |
+
|
115 |
+
case 2: // top
|
116 |
+
|
117 |
+
// generate UVs along X then Z
|
118 |
+
faceDirVector.set( 0, 1, 0 );
|
119 |
+
uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width );
|
120 |
+
uvs[ j + 1 ] = getUv( faceDirVector, normal, 'z', 'x', radius, depth );
|
121 |
+
break;
|
122 |
+
|
123 |
+
case 3: // bottom
|
124 |
+
|
125 |
+
// generate UVs along X then Z
|
126 |
+
faceDirVector.set( 0, - 1, 0 );
|
127 |
+
uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width );
|
128 |
+
uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'x', radius, depth );
|
129 |
+
break;
|
130 |
+
|
131 |
+
case 4: // front
|
132 |
+
|
133 |
+
// generate UVs along X then Y
|
134 |
+
faceDirVector.set( 0, 0, 1 );
|
135 |
+
uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'y', radius, width );
|
136 |
+
uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height );
|
137 |
+
break;
|
138 |
+
|
139 |
+
case 5: // back
|
140 |
+
|
141 |
+
// generate UVs along X then Y
|
142 |
+
faceDirVector.set( 0, 0, - 1 );
|
143 |
+
uvs[ j + 0 ] = getUv( faceDirVector, normal, 'x', 'y', radius, width );
|
144 |
+
uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height );
|
145 |
+
break;
|
146 |
+
|
147 |
+
}
|
148 |
+
|
149 |
+
}
|
150 |
+
|
151 |
+
}
|
152 |
+
|
153 |
+
}
|
154 |
+
|
155 |
+
export { RoundedBoxGeometry };
|
libs/three.js/0.172.0/jsm/geometries/TeapotGeometry.js
ADDED
@@ -0,0 +1,704 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import {
|
2 |
+
BufferAttribute,
|
3 |
+
BufferGeometry,
|
4 |
+
Matrix4,
|
5 |
+
Vector3,
|
6 |
+
Vector4
|
7 |
+
} from 'three';
|
8 |
+
|
9 |
+
/**
|
10 |
+
* Tessellates the famous Utah teapot database by Martin Newell into triangles.
|
11 |
+
*
|
12 |
+
* Parameters: size = 50, segments = 10, bottom = true, lid = true, body = true,
|
13 |
+
* fitLid = true, blinn = true
|
14 |
+
*
|
15 |
+
* size is a relative scale: I've scaled the teapot to fit vertically between -1 and 1.
|
16 |
+
* Think of it as a "radius".
|
17 |
+
* segments - number of line segments to subdivide each patch edge;
|
18 |
+
* 1 is possible but gives degenerates, so two is the real minimum.
|
19 |
+
* bottom - boolean, if true (default) then the bottom patches are added. Some consider
|
20 |
+
* adding the bottom heresy, so set this to "false" to adhere to the One True Way.
|
21 |
+
* lid - to remove the lid and look inside, set to true.
|
22 |
+
* body - to remove the body and leave the lid, set this and "bottom" to false.
|
23 |
+
* fitLid - the lid is a tad small in the original. This stretches it a bit so you can't
|
24 |
+
* see the teapot's insides through the gap.
|
25 |
+
* blinn - Jim Blinn scaled the original data vertically by dividing by about 1.3 to look
|
26 |
+
* nicer. If you want to see the original teapot, similar to the real-world model, set
|
27 |
+
* this to false. True by default.
|
28 |
+
* See http://en.wikipedia.org/wiki/File:Original_Utah_Teapot.jpg for the original
|
29 |
+
* real-world teapot (from http://en.wikipedia.org/wiki/Utah_teapot).
|
30 |
+
*
|
31 |
+
* Note that the bottom (the last four patches) is not flat - blame Frank Crow, not me.
|
32 |
+
*
|
33 |
+
* The teapot should normally be rendered as a double sided object, since for some
|
34 |
+
* patches both sides can be seen, e.g., the gap around the lid and inside the spout.
|
35 |
+
*
|
36 |
+
* Segments 'n' determines the number of triangles output.
|
37 |
+
* Total triangles = 32*2*n*n - 8*n [degenerates at the top and bottom cusps are deleted]
|
38 |
+
*
|
39 |
+
* size_factor # triangles
|
40 |
+
* 1 56
|
41 |
+
* 2 240
|
42 |
+
* 3 552
|
43 |
+
* 4 992
|
44 |
+
*
|
45 |
+
* 10 6320
|
46 |
+
* 20 25440
|
47 |
+
* 30 57360
|
48 |
+
*
|
49 |
+
* Code converted from my ancient SPD software, http://tog.acm.org/resources/SPD/
|
50 |
+
* Created for the Udacity course "Interactive Rendering", http://bit.ly/ericity
|
51 |
+
* YouTube video on teapot history: https://www.youtube.com/watch?v=DxMfblPzFNc
|
52 |
+
*
|
53 |
+
* See https://en.wikipedia.org/wiki/Utah_teapot for the history of the teapot
|
54 |
+
*
|
55 |
+
*/
|
56 |
+
|
57 |
+
class TeapotGeometry extends BufferGeometry {
|
58 |
+
|
59 |
+
constructor( size = 50, segments = 10, bottom = true, lid = true, body = true, fitLid = true, blinn = true ) {
|
60 |
+
|
61 |
+
// 32 * 4 * 4 Bezier spline patches
|
62 |
+
const teapotPatches = [
|
63 |
+
/*rim*/
|
64 |
+
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
65 |
+
3, 16, 17, 18, 7, 19, 20, 21, 11, 22, 23, 24, 15, 25, 26, 27,
|
66 |
+
18, 28, 29, 30, 21, 31, 32, 33, 24, 34, 35, 36, 27, 37, 38, 39,
|
67 |
+
30, 40, 41, 0, 33, 42, 43, 4, 36, 44, 45, 8, 39, 46, 47, 12,
|
68 |
+
/*body*/
|
69 |
+
12, 13, 14, 15, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59,
|
70 |
+
15, 25, 26, 27, 51, 60, 61, 62, 55, 63, 64, 65, 59, 66, 67, 68,
|
71 |
+
27, 37, 38, 39, 62, 69, 70, 71, 65, 72, 73, 74, 68, 75, 76, 77,
|
72 |
+
39, 46, 47, 12, 71, 78, 79, 48, 74, 80, 81, 52, 77, 82, 83, 56,
|
73 |
+
56, 57, 58, 59, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95,
|
74 |
+
59, 66, 67, 68, 87, 96, 97, 98, 91, 99, 100, 101, 95, 102, 103, 104,
|
75 |
+
68, 75, 76, 77, 98, 105, 106, 107, 101, 108, 109, 110, 104, 111, 112, 113,
|
76 |
+
77, 82, 83, 56, 107, 114, 115, 84, 110, 116, 117, 88, 113, 118, 119, 92,
|
77 |
+
/*handle*/
|
78 |
+
120, 121, 122, 123, 124, 125, 126, 127, 128, 129, 130, 131, 132, 133, 134, 135,
|
79 |
+
123, 136, 137, 120, 127, 138, 139, 124, 131, 140, 141, 128, 135, 142, 143, 132,
|
80 |
+
132, 133, 134, 135, 144, 145, 146, 147, 148, 149, 150, 151, 68, 152, 153, 154,
|
81 |
+
135, 142, 143, 132, 147, 155, 156, 144, 151, 157, 158, 148, 154, 159, 160, 68,
|
82 |
+
/*spout*/
|
83 |
+
161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172, 173, 174, 175, 176,
|
84 |
+
164, 177, 178, 161, 168, 179, 180, 165, 172, 181, 182, 169, 176, 183, 184, 173,
|
85 |
+
173, 174, 175, 176, 185, 186, 187, 188, 189, 190, 191, 192, 193, 194, 195, 196,
|
86 |
+
176, 183, 184, 173, 188, 197, 198, 185, 192, 199, 200, 189, 196, 201, 202, 193,
|
87 |
+
/*lid*/
|
88 |
+
203, 203, 203, 203, 204, 205, 206, 207, 208, 208, 208, 208, 209, 210, 211, 212,
|
89 |
+
203, 203, 203, 203, 207, 213, 214, 215, 208, 208, 208, 208, 212, 216, 217, 218,
|
90 |
+
203, 203, 203, 203, 215, 219, 220, 221, 208, 208, 208, 208, 218, 222, 223, 224,
|
91 |
+
203, 203, 203, 203, 221, 225, 226, 204, 208, 208, 208, 208, 224, 227, 228, 209,
|
92 |
+
209, 210, 211, 212, 229, 230, 231, 232, 233, 234, 235, 236, 237, 238, 239, 240,
|
93 |
+
212, 216, 217, 218, 232, 241, 242, 243, 236, 244, 245, 246, 240, 247, 248, 249,
|
94 |
+
218, 222, 223, 224, 243, 250, 251, 252, 246, 253, 254, 255, 249, 256, 257, 258,
|
95 |
+
224, 227, 228, 209, 252, 259, 260, 229, 255, 261, 262, 233, 258, 263, 264, 237,
|
96 |
+
/*bottom*/
|
97 |
+
265, 265, 265, 265, 266, 267, 268, 269, 270, 271, 272, 273, 92, 119, 118, 113,
|
98 |
+
265, 265, 265, 265, 269, 274, 275, 276, 273, 277, 278, 279, 113, 112, 111, 104,
|
99 |
+
265, 265, 265, 265, 276, 280, 281, 282, 279, 283, 284, 285, 104, 103, 102, 95,
|
100 |
+
265, 265, 265, 265, 282, 286, 287, 266, 285, 288, 289, 270, 95, 94, 93, 92
|
101 |
+
];
|
102 |
+
|
103 |
+
const teapotVertices = [
|
104 |
+
1.4, 0, 2.4,
|
105 |
+
1.4, - 0.784, 2.4,
|
106 |
+
0.784, - 1.4, 2.4,
|
107 |
+
0, - 1.4, 2.4,
|
108 |
+
1.3375, 0, 2.53125,
|
109 |
+
1.3375, - 0.749, 2.53125,
|
110 |
+
0.749, - 1.3375, 2.53125,
|
111 |
+
0, - 1.3375, 2.53125,
|
112 |
+
1.4375, 0, 2.53125,
|
113 |
+
1.4375, - 0.805, 2.53125,
|
114 |
+
0.805, - 1.4375, 2.53125,
|
115 |
+
0, - 1.4375, 2.53125,
|
116 |
+
1.5, 0, 2.4,
|
117 |
+
1.5, - 0.84, 2.4,
|
118 |
+
0.84, - 1.5, 2.4,
|
119 |
+
0, - 1.5, 2.4,
|
120 |
+
- 0.784, - 1.4, 2.4,
|
121 |
+
- 1.4, - 0.784, 2.4,
|
122 |
+
- 1.4, 0, 2.4,
|
123 |
+
- 0.749, - 1.3375, 2.53125,
|
124 |
+
- 1.3375, - 0.749, 2.53125,
|
125 |
+
- 1.3375, 0, 2.53125,
|
126 |
+
- 0.805, - 1.4375, 2.53125,
|
127 |
+
- 1.4375, - 0.805, 2.53125,
|
128 |
+
- 1.4375, 0, 2.53125,
|
129 |
+
- 0.84, - 1.5, 2.4,
|
130 |
+
- 1.5, - 0.84, 2.4,
|
131 |
+
- 1.5, 0, 2.4,
|
132 |
+
- 1.4, 0.784, 2.4,
|
133 |
+
- 0.784, 1.4, 2.4,
|
134 |
+
0, 1.4, 2.4,
|
135 |
+
- 1.3375, 0.749, 2.53125,
|
136 |
+
- 0.749, 1.3375, 2.53125,
|
137 |
+
0, 1.3375, 2.53125,
|
138 |
+
- 1.4375, 0.805, 2.53125,
|
139 |
+
- 0.805, 1.4375, 2.53125,
|
140 |
+
0, 1.4375, 2.53125,
|
141 |
+
- 1.5, 0.84, 2.4,
|
142 |
+
- 0.84, 1.5, 2.4,
|
143 |
+
0, 1.5, 2.4,
|
144 |
+
0.784, 1.4, 2.4,
|
145 |
+
1.4, 0.784, 2.4,
|
146 |
+
0.749, 1.3375, 2.53125,
|
147 |
+
1.3375, 0.749, 2.53125,
|
148 |
+
0.805, 1.4375, 2.53125,
|
149 |
+
1.4375, 0.805, 2.53125,
|
150 |
+
0.84, 1.5, 2.4,
|
151 |
+
1.5, 0.84, 2.4,
|
152 |
+
1.75, 0, 1.875,
|
153 |
+
1.75, - 0.98, 1.875,
|
154 |
+
0.98, - 1.75, 1.875,
|
155 |
+
0, - 1.75, 1.875,
|
156 |
+
2, 0, 1.35,
|
157 |
+
2, - 1.12, 1.35,
|
158 |
+
1.12, - 2, 1.35,
|
159 |
+
0, - 2, 1.35,
|
160 |
+
2, 0, 0.9,
|
161 |
+
2, - 1.12, 0.9,
|
162 |
+
1.12, - 2, 0.9,
|
163 |
+
0, - 2, 0.9,
|
164 |
+
- 0.98, - 1.75, 1.875,
|
165 |
+
- 1.75, - 0.98, 1.875,
|
166 |
+
- 1.75, 0, 1.875,
|
167 |
+
- 1.12, - 2, 1.35,
|
168 |
+
- 2, - 1.12, 1.35,
|
169 |
+
- 2, 0, 1.35,
|
170 |
+
- 1.12, - 2, 0.9,
|
171 |
+
- 2, - 1.12, 0.9,
|
172 |
+
- 2, 0, 0.9,
|
173 |
+
- 1.75, 0.98, 1.875,
|
174 |
+
- 0.98, 1.75, 1.875,
|
175 |
+
0, 1.75, 1.875,
|
176 |
+
- 2, 1.12, 1.35,
|
177 |
+
- 1.12, 2, 1.35,
|
178 |
+
0, 2, 1.35,
|
179 |
+
- 2, 1.12, 0.9,
|
180 |
+
- 1.12, 2, 0.9,
|
181 |
+
0, 2, 0.9,
|
182 |
+
0.98, 1.75, 1.875,
|
183 |
+
1.75, 0.98, 1.875,
|
184 |
+
1.12, 2, 1.35,
|
185 |
+
2, 1.12, 1.35,
|
186 |
+
1.12, 2, 0.9,
|
187 |
+
2, 1.12, 0.9,
|
188 |
+
2, 0, 0.45,
|
189 |
+
2, - 1.12, 0.45,
|
190 |
+
1.12, - 2, 0.45,
|
191 |
+
0, - 2, 0.45,
|
192 |
+
1.5, 0, 0.225,
|
193 |
+
1.5, - 0.84, 0.225,
|
194 |
+
0.84, - 1.5, 0.225,
|
195 |
+
0, - 1.5, 0.225,
|
196 |
+
1.5, 0, 0.15,
|
197 |
+
1.5, - 0.84, 0.15,
|
198 |
+
0.84, - 1.5, 0.15,
|
199 |
+
0, - 1.5, 0.15,
|
200 |
+
- 1.12, - 2, 0.45,
|
201 |
+
- 2, - 1.12, 0.45,
|
202 |
+
- 2, 0, 0.45,
|
203 |
+
- 0.84, - 1.5, 0.225,
|
204 |
+
- 1.5, - 0.84, 0.225,
|
205 |
+
- 1.5, 0, 0.225,
|
206 |
+
- 0.84, - 1.5, 0.15,
|
207 |
+
- 1.5, - 0.84, 0.15,
|
208 |
+
- 1.5, 0, 0.15,
|
209 |
+
- 2, 1.12, 0.45,
|
210 |
+
- 1.12, 2, 0.45,
|
211 |
+
0, 2, 0.45,
|
212 |
+
- 1.5, 0.84, 0.225,
|
213 |
+
- 0.84, 1.5, 0.225,
|
214 |
+
0, 1.5, 0.225,
|
215 |
+
- 1.5, 0.84, 0.15,
|
216 |
+
- 0.84, 1.5, 0.15,
|
217 |
+
0, 1.5, 0.15,
|
218 |
+
1.12, 2, 0.45,
|
219 |
+
2, 1.12, 0.45,
|
220 |
+
0.84, 1.5, 0.225,
|
221 |
+
1.5, 0.84, 0.225,
|
222 |
+
0.84, 1.5, 0.15,
|
223 |
+
1.5, 0.84, 0.15,
|
224 |
+
- 1.6, 0, 2.025,
|
225 |
+
- 1.6, - 0.3, 2.025,
|
226 |
+
- 1.5, - 0.3, 2.25,
|
227 |
+
- 1.5, 0, 2.25,
|
228 |
+
- 2.3, 0, 2.025,
|
229 |
+
- 2.3, - 0.3, 2.025,
|
230 |
+
- 2.5, - 0.3, 2.25,
|
231 |
+
- 2.5, 0, 2.25,
|
232 |
+
- 2.7, 0, 2.025,
|
233 |
+
- 2.7, - 0.3, 2.025,
|
234 |
+
- 3, - 0.3, 2.25,
|
235 |
+
- 3, 0, 2.25,
|
236 |
+
- 2.7, 0, 1.8,
|
237 |
+
- 2.7, - 0.3, 1.8,
|
238 |
+
- 3, - 0.3, 1.8,
|
239 |
+
- 3, 0, 1.8,
|
240 |
+
- 1.5, 0.3, 2.25,
|
241 |
+
- 1.6, 0.3, 2.025,
|
242 |
+
- 2.5, 0.3, 2.25,
|
243 |
+
- 2.3, 0.3, 2.025,
|
244 |
+
- 3, 0.3, 2.25,
|
245 |
+
- 2.7, 0.3, 2.025,
|
246 |
+
- 3, 0.3, 1.8,
|
247 |
+
- 2.7, 0.3, 1.8,
|
248 |
+
- 2.7, 0, 1.575,
|
249 |
+
- 2.7, - 0.3, 1.575,
|
250 |
+
- 3, - 0.3, 1.35,
|
251 |
+
- 3, 0, 1.35,
|
252 |
+
- 2.5, 0, 1.125,
|
253 |
+
- 2.5, - 0.3, 1.125,
|
254 |
+
- 2.65, - 0.3, 0.9375,
|
255 |
+
- 2.65, 0, 0.9375,
|
256 |
+
- 2, - 0.3, 0.9,
|
257 |
+
- 1.9, - 0.3, 0.6,
|
258 |
+
- 1.9, 0, 0.6,
|
259 |
+
- 3, 0.3, 1.35,
|
260 |
+
- 2.7, 0.3, 1.575,
|
261 |
+
- 2.65, 0.3, 0.9375,
|
262 |
+
- 2.5, 0.3, 1.125,
|
263 |
+
- 1.9, 0.3, 0.6,
|
264 |
+
- 2, 0.3, 0.9,
|
265 |
+
1.7, 0, 1.425,
|
266 |
+
1.7, - 0.66, 1.425,
|
267 |
+
1.7, - 0.66, 0.6,
|
268 |
+
1.7, 0, 0.6,
|
269 |
+
2.6, 0, 1.425,
|
270 |
+
2.6, - 0.66, 1.425,
|
271 |
+
3.1, - 0.66, 0.825,
|
272 |
+
3.1, 0, 0.825,
|
273 |
+
2.3, 0, 2.1,
|
274 |
+
2.3, - 0.25, 2.1,
|
275 |
+
2.4, - 0.25, 2.025,
|
276 |
+
2.4, 0, 2.025,
|
277 |
+
2.7, 0, 2.4,
|
278 |
+
2.7, - 0.25, 2.4,
|
279 |
+
3.3, - 0.25, 2.4,
|
280 |
+
3.3, 0, 2.4,
|
281 |
+
1.7, 0.66, 0.6,
|
282 |
+
1.7, 0.66, 1.425,
|
283 |
+
3.1, 0.66, 0.825,
|
284 |
+
2.6, 0.66, 1.425,
|
285 |
+
2.4, 0.25, 2.025,
|
286 |
+
2.3, 0.25, 2.1,
|
287 |
+
3.3, 0.25, 2.4,
|
288 |
+
2.7, 0.25, 2.4,
|
289 |
+
2.8, 0, 2.475,
|
290 |
+
2.8, - 0.25, 2.475,
|
291 |
+
3.525, - 0.25, 2.49375,
|
292 |
+
3.525, 0, 2.49375,
|
293 |
+
2.9, 0, 2.475,
|
294 |
+
2.9, - 0.15, 2.475,
|
295 |
+
3.45, - 0.15, 2.5125,
|
296 |
+
3.45, 0, 2.5125,
|
297 |
+
2.8, 0, 2.4,
|
298 |
+
2.8, - 0.15, 2.4,
|
299 |
+
3.2, - 0.15, 2.4,
|
300 |
+
3.2, 0, 2.4,
|
301 |
+
3.525, 0.25, 2.49375,
|
302 |
+
2.8, 0.25, 2.475,
|
303 |
+
3.45, 0.15, 2.5125,
|
304 |
+
2.9, 0.15, 2.475,
|
305 |
+
3.2, 0.15, 2.4,
|
306 |
+
2.8, 0.15, 2.4,
|
307 |
+
0, 0, 3.15,
|
308 |
+
0.8, 0, 3.15,
|
309 |
+
0.8, - 0.45, 3.15,
|
310 |
+
0.45, - 0.8, 3.15,
|
311 |
+
0, - 0.8, 3.15,
|
312 |
+
0, 0, 2.85,
|
313 |
+
0.2, 0, 2.7,
|
314 |
+
0.2, - 0.112, 2.7,
|
315 |
+
0.112, - 0.2, 2.7,
|
316 |
+
0, - 0.2, 2.7,
|
317 |
+
- 0.45, - 0.8, 3.15,
|
318 |
+
- 0.8, - 0.45, 3.15,
|
319 |
+
- 0.8, 0, 3.15,
|
320 |
+
- 0.112, - 0.2, 2.7,
|
321 |
+
- 0.2, - 0.112, 2.7,
|
322 |
+
- 0.2, 0, 2.7,
|
323 |
+
- 0.8, 0.45, 3.15,
|
324 |
+
- 0.45, 0.8, 3.15,
|
325 |
+
0, 0.8, 3.15,
|
326 |
+
- 0.2, 0.112, 2.7,
|
327 |
+
- 0.112, 0.2, 2.7,
|
328 |
+
0, 0.2, 2.7,
|
329 |
+
0.45, 0.8, 3.15,
|
330 |
+
0.8, 0.45, 3.15,
|
331 |
+
0.112, 0.2, 2.7,
|
332 |
+
0.2, 0.112, 2.7,
|
333 |
+
0.4, 0, 2.55,
|
334 |
+
0.4, - 0.224, 2.55,
|
335 |
+
0.224, - 0.4, 2.55,
|
336 |
+
0, - 0.4, 2.55,
|
337 |
+
1.3, 0, 2.55,
|
338 |
+
1.3, - 0.728, 2.55,
|
339 |
+
0.728, - 1.3, 2.55,
|
340 |
+
0, - 1.3, 2.55,
|
341 |
+
1.3, 0, 2.4,
|
342 |
+
1.3, - 0.728, 2.4,
|
343 |
+
0.728, - 1.3, 2.4,
|
344 |
+
0, - 1.3, 2.4,
|
345 |
+
- 0.224, - 0.4, 2.55,
|
346 |
+
- 0.4, - 0.224, 2.55,
|
347 |
+
- 0.4, 0, 2.55,
|
348 |
+
- 0.728, - 1.3, 2.55,
|
349 |
+
- 1.3, - 0.728, 2.55,
|
350 |
+
- 1.3, 0, 2.55,
|
351 |
+
- 0.728, - 1.3, 2.4,
|
352 |
+
- 1.3, - 0.728, 2.4,
|
353 |
+
- 1.3, 0, 2.4,
|
354 |
+
- 0.4, 0.224, 2.55,
|
355 |
+
- 0.224, 0.4, 2.55,
|
356 |
+
0, 0.4, 2.55,
|
357 |
+
- 1.3, 0.728, 2.55,
|
358 |
+
- 0.728, 1.3, 2.55,
|
359 |
+
0, 1.3, 2.55,
|
360 |
+
- 1.3, 0.728, 2.4,
|
361 |
+
- 0.728, 1.3, 2.4,
|
362 |
+
0, 1.3, 2.4,
|
363 |
+
0.224, 0.4, 2.55,
|
364 |
+
0.4, 0.224, 2.55,
|
365 |
+
0.728, 1.3, 2.55,
|
366 |
+
1.3, 0.728, 2.55,
|
367 |
+
0.728, 1.3, 2.4,
|
368 |
+
1.3, 0.728, 2.4,
|
369 |
+
0, 0, 0,
|
370 |
+
1.425, 0, 0,
|
371 |
+
1.425, 0.798, 0,
|
372 |
+
0.798, 1.425, 0,
|
373 |
+
0, 1.425, 0,
|
374 |
+
1.5, 0, 0.075,
|
375 |
+
1.5, 0.84, 0.075,
|
376 |
+
0.84, 1.5, 0.075,
|
377 |
+
0, 1.5, 0.075,
|
378 |
+
- 0.798, 1.425, 0,
|
379 |
+
- 1.425, 0.798, 0,
|
380 |
+
- 1.425, 0, 0,
|
381 |
+
- 0.84, 1.5, 0.075,
|
382 |
+
- 1.5, 0.84, 0.075,
|
383 |
+
- 1.5, 0, 0.075,
|
384 |
+
- 1.425, - 0.798, 0,
|
385 |
+
- 0.798, - 1.425, 0,
|
386 |
+
0, - 1.425, 0,
|
387 |
+
- 1.5, - 0.84, 0.075,
|
388 |
+
- 0.84, - 1.5, 0.075,
|
389 |
+
0, - 1.5, 0.075,
|
390 |
+
0.798, - 1.425, 0,
|
391 |
+
1.425, - 0.798, 0,
|
392 |
+
0.84, - 1.5, 0.075,
|
393 |
+
1.5, - 0.84, 0.075
|
394 |
+
];
|
395 |
+
|
396 |
+
super();
|
397 |
+
|
398 |
+
// number of segments per patch
|
399 |
+
segments = Math.max( 2, Math.floor( segments ) );
|
400 |
+
|
401 |
+
// Jim Blinn scaled the teapot down in size by about 1.3 for
|
402 |
+
// some rendering tests. He liked the new proportions that he kept
|
403 |
+
// the data in this form. The model was distributed with these new
|
404 |
+
// proportions and became the norm. Trivia: comparing images of the
|
405 |
+
// real teapot and the computer model, the ratio for the bowl of the
|
406 |
+
// real teapot is more like 1.25, but since 1.3 is the traditional
|
407 |
+
// value given, we use it here.
|
408 |
+
const blinnScale = 1.3;
|
409 |
+
|
410 |
+
// scale the size to be the real scaling factor
|
411 |
+
const maxHeight = 3.15 * ( blinn ? 1 : blinnScale );
|
412 |
+
|
413 |
+
const maxHeight2 = maxHeight / 2;
|
414 |
+
const trueSize = size / maxHeight2;
|
415 |
+
|
416 |
+
// Number of elements depends on what is needed. Subtract degenerate
|
417 |
+
// triangles at tip of bottom and lid out in advance.
|
418 |
+
let numTriangles = bottom ? ( 8 * segments - 4 ) * segments : 0;
|
419 |
+
numTriangles += lid ? ( 16 * segments - 4 ) * segments : 0;
|
420 |
+
numTriangles += body ? 40 * segments * segments : 0;
|
421 |
+
|
422 |
+
const indices = new Uint32Array( numTriangles * 3 );
|
423 |
+
|
424 |
+
let numVertices = bottom ? 4 : 0;
|
425 |
+
numVertices += lid ? 8 : 0;
|
426 |
+
numVertices += body ? 20 : 0;
|
427 |
+
numVertices *= ( segments + 1 ) * ( segments + 1 );
|
428 |
+
|
429 |
+
const vertices = new Float32Array( numVertices * 3 );
|
430 |
+
const normals = new Float32Array( numVertices * 3 );
|
431 |
+
const uvs = new Float32Array( numVertices * 2 );
|
432 |
+
|
433 |
+
// Bezier form
|
434 |
+
const ms = new Matrix4();
|
435 |
+
ms.set(
|
436 |
+
- 1.0, 3.0, - 3.0, 1.0,
|
437 |
+
3.0, - 6.0, 3.0, 0.0,
|
438 |
+
- 3.0, 3.0, 0.0, 0.0,
|
439 |
+
1.0, 0.0, 0.0, 0.0 );
|
440 |
+
|
441 |
+
const g = [];
|
442 |
+
|
443 |
+
const sp = [];
|
444 |
+
const tp = [];
|
445 |
+
const dsp = [];
|
446 |
+
const dtp = [];
|
447 |
+
|
448 |
+
// M * G * M matrix, sort of see
|
449 |
+
// http://www.cs.helsinki.fi/group/goa/mallinnus/curves/surfaces.html
|
450 |
+
const mgm = [];
|
451 |
+
|
452 |
+
const vert = [];
|
453 |
+
const sdir = [];
|
454 |
+
const tdir = [];
|
455 |
+
|
456 |
+
const norm = new Vector3();
|
457 |
+
|
458 |
+
let tcoord;
|
459 |
+
|
460 |
+
let sval;
|
461 |
+
let tval;
|
462 |
+
let p;
|
463 |
+
let dsval = 0;
|
464 |
+
let dtval = 0;
|
465 |
+
|
466 |
+
const normOut = new Vector3();
|
467 |
+
|
468 |
+
const gmx = new Matrix4();
|
469 |
+
const tmtx = new Matrix4();
|
470 |
+
|
471 |
+
const vsp = new Vector4();
|
472 |
+
const vtp = new Vector4();
|
473 |
+
const vdsp = new Vector4();
|
474 |
+
const vdtp = new Vector4();
|
475 |
+
|
476 |
+
const vsdir = new Vector3();
|
477 |
+
const vtdir = new Vector3();
|
478 |
+
|
479 |
+
const mst = ms.clone();
|
480 |
+
mst.transpose();
|
481 |
+
|
482 |
+
// internal function: test if triangle has any matching vertices;
|
483 |
+
// if so, don't save triangle, since it won't display anything.
|
484 |
+
const notDegenerate = ( vtx1, vtx2, vtx3 ) => // if any vertex matches, return false
|
485 |
+
! ( ( ( vertices[ vtx1 * 3 ] === vertices[ vtx2 * 3 ] ) &&
|
486 |
+
( vertices[ vtx1 * 3 + 1 ] === vertices[ vtx2 * 3 + 1 ] ) &&
|
487 |
+
( vertices[ vtx1 * 3 + 2 ] === vertices[ vtx2 * 3 + 2 ] ) ) ||
|
488 |
+
( ( vertices[ vtx1 * 3 ] === vertices[ vtx3 * 3 ] ) &&
|
489 |
+
( vertices[ vtx1 * 3 + 1 ] === vertices[ vtx3 * 3 + 1 ] ) &&
|
490 |
+
( vertices[ vtx1 * 3 + 2 ] === vertices[ vtx3 * 3 + 2 ] ) ) || ( vertices[ vtx2 * 3 ] === vertices[ vtx3 * 3 ] ) &&
|
491 |
+
( vertices[ vtx2 * 3 + 1 ] === vertices[ vtx3 * 3 + 1 ] ) &&
|
492 |
+
( vertices[ vtx2 * 3 + 2 ] === vertices[ vtx3 * 3 + 2 ] ) );
|
493 |
+
|
494 |
+
|
495 |
+
for ( let i = 0; i < 3; i ++ ) {
|
496 |
+
|
497 |
+
mgm[ i ] = new Matrix4();
|
498 |
+
|
499 |
+
}
|
500 |
+
|
501 |
+
const minPatches = body ? 0 : 20;
|
502 |
+
const maxPatches = bottom ? 32 : 28;
|
503 |
+
|
504 |
+
const vertPerRow = segments + 1;
|
505 |
+
|
506 |
+
let surfCount = 0;
|
507 |
+
|
508 |
+
let vertCount = 0;
|
509 |
+
let normCount = 0;
|
510 |
+
let uvCount = 0;
|
511 |
+
|
512 |
+
let indexCount = 0;
|
513 |
+
|
514 |
+
for ( let surf = minPatches; surf < maxPatches; surf ++ ) {
|
515 |
+
|
516 |
+
// lid is in the middle of the data, patches 20-27,
|
517 |
+
// so ignore it for this part of the loop if the lid is not desired
|
518 |
+
if ( lid || ( surf < 20 || surf >= 28 ) ) {
|
519 |
+
|
520 |
+
// get M * G * M matrix for x,y,z
|
521 |
+
for ( let i = 0; i < 3; i ++ ) {
|
522 |
+
|
523 |
+
// get control patches
|
524 |
+
for ( let r = 0; r < 4; r ++ ) {
|
525 |
+
|
526 |
+
for ( let c = 0; c < 4; c ++ ) {
|
527 |
+
|
528 |
+
// transposed
|
529 |
+
g[ c * 4 + r ] = teapotVertices[ teapotPatches[ surf * 16 + r * 4 + c ] * 3 + i ];
|
530 |
+
|
531 |
+
// is the lid to be made larger, and is this a point on the lid
|
532 |
+
// that is X or Y?
|
533 |
+
if ( fitLid && ( surf >= 20 && surf < 28 ) && ( i !== 2 ) ) {
|
534 |
+
|
535 |
+
// increase XY size by 7.7%, found empirically. I don't
|
536 |
+
// increase Z so that the teapot will continue to fit in the
|
537 |
+
// space -1 to 1 for Y (Y is up for the final model).
|
538 |
+
g[ c * 4 + r ] *= 1.077;
|
539 |
+
|
540 |
+
}
|
541 |
+
|
542 |
+
// Blinn "fixed" the teapot by dividing Z by blinnScale, and that's the
|
543 |
+
// data we now use. The original teapot is taller. Fix it:
|
544 |
+
if ( ! blinn && ( i === 2 ) ) {
|
545 |
+
|
546 |
+
g[ c * 4 + r ] *= blinnScale;
|
547 |
+
|
548 |
+
}
|
549 |
+
|
550 |
+
}
|
551 |
+
|
552 |
+
}
|
553 |
+
|
554 |
+
gmx.set( g[ 0 ], g[ 1 ], g[ 2 ], g[ 3 ], g[ 4 ], g[ 5 ], g[ 6 ], g[ 7 ], g[ 8 ], g[ 9 ], g[ 10 ], g[ 11 ], g[ 12 ], g[ 13 ], g[ 14 ], g[ 15 ] );
|
555 |
+
|
556 |
+
tmtx.multiplyMatrices( gmx, ms );
|
557 |
+
mgm[ i ].multiplyMatrices( mst, tmtx );
|
558 |
+
|
559 |
+
}
|
560 |
+
|
561 |
+
// step along, get points, and output
|
562 |
+
for ( let sstep = 0; sstep <= segments; sstep ++ ) {
|
563 |
+
|
564 |
+
const s = sstep / segments;
|
565 |
+
|
566 |
+
for ( let tstep = 0; tstep <= segments; tstep ++ ) {
|
567 |
+
|
568 |
+
const t = tstep / segments;
|
569 |
+
|
570 |
+
// point from basis
|
571 |
+
// get power vectors and their derivatives
|
572 |
+
for ( p = 4, sval = tval = 1.0; p --; ) {
|
573 |
+
|
574 |
+
sp[ p ] = sval;
|
575 |
+
tp[ p ] = tval;
|
576 |
+
sval *= s;
|
577 |
+
tval *= t;
|
578 |
+
|
579 |
+
if ( p === 3 ) {
|
580 |
+
|
581 |
+
dsp[ p ] = dtp[ p ] = 0.0;
|
582 |
+
dsval = dtval = 1.0;
|
583 |
+
|
584 |
+
} else {
|
585 |
+
|
586 |
+
dsp[ p ] = dsval * ( 3 - p );
|
587 |
+
dtp[ p ] = dtval * ( 3 - p );
|
588 |
+
dsval *= s;
|
589 |
+
dtval *= t;
|
590 |
+
|
591 |
+
}
|
592 |
+
|
593 |
+
}
|
594 |
+
|
595 |
+
vsp.fromArray( sp );
|
596 |
+
vtp.fromArray( tp );
|
597 |
+
vdsp.fromArray( dsp );
|
598 |
+
vdtp.fromArray( dtp );
|
599 |
+
|
600 |
+
// do for x,y,z
|
601 |
+
for ( let i = 0; i < 3; i ++ ) {
|
602 |
+
|
603 |
+
// multiply power vectors times matrix to get value
|
604 |
+
tcoord = vsp.clone();
|
605 |
+
tcoord.applyMatrix4( mgm[ i ] );
|
606 |
+
vert[ i ] = tcoord.dot( vtp );
|
607 |
+
|
608 |
+
// get s and t tangent vectors
|
609 |
+
tcoord = vdsp.clone();
|
610 |
+
tcoord.applyMatrix4( mgm[ i ] );
|
611 |
+
sdir[ i ] = tcoord.dot( vtp );
|
612 |
+
|
613 |
+
tcoord = vsp.clone();
|
614 |
+
tcoord.applyMatrix4( mgm[ i ] );
|
615 |
+
tdir[ i ] = tcoord.dot( vdtp );
|
616 |
+
|
617 |
+
}
|
618 |
+
|
619 |
+
// find normal
|
620 |
+
vsdir.fromArray( sdir );
|
621 |
+
vtdir.fromArray( tdir );
|
622 |
+
norm.crossVectors( vtdir, vsdir );
|
623 |
+
norm.normalize();
|
624 |
+
|
625 |
+
// if X and Z length is 0, at the cusp, so point the normal up or down, depending on patch number
|
626 |
+
if ( vert[ 0 ] === 0 && vert[ 1 ] === 0 ) {
|
627 |
+
|
628 |
+
// if above the middle of the teapot, normal points up, else down
|
629 |
+
normOut.set( 0, vert[ 2 ] > maxHeight2 ? 1 : - 1, 0 );
|
630 |
+
|
631 |
+
} else {
|
632 |
+
|
633 |
+
// standard output: rotate on X axis
|
634 |
+
normOut.set( norm.x, norm.z, - norm.y );
|
635 |
+
|
636 |
+
}
|
637 |
+
|
638 |
+
// store it all
|
639 |
+
vertices[ vertCount ++ ] = trueSize * vert[ 0 ];
|
640 |
+
vertices[ vertCount ++ ] = trueSize * ( vert[ 2 ] - maxHeight2 );
|
641 |
+
vertices[ vertCount ++ ] = - trueSize * vert[ 1 ];
|
642 |
+
|
643 |
+
normals[ normCount ++ ] = normOut.x;
|
644 |
+
normals[ normCount ++ ] = normOut.y;
|
645 |
+
normals[ normCount ++ ] = normOut.z;
|
646 |
+
|
647 |
+
uvs[ uvCount ++ ] = 1 - t;
|
648 |
+
uvs[ uvCount ++ ] = 1 - s;
|
649 |
+
|
650 |
+
}
|
651 |
+
|
652 |
+
}
|
653 |
+
|
654 |
+
// save the faces
|
655 |
+
for ( let sstep = 0; sstep < segments; sstep ++ ) {
|
656 |
+
|
657 |
+
for ( let tstep = 0; tstep < segments; tstep ++ ) {
|
658 |
+
|
659 |
+
const v1 = surfCount * vertPerRow * vertPerRow + sstep * vertPerRow + tstep;
|
660 |
+
const v2 = v1 + 1;
|
661 |
+
const v3 = v2 + vertPerRow;
|
662 |
+
const v4 = v1 + vertPerRow;
|
663 |
+
|
664 |
+
// Normals and UVs cannot be shared. Without clone(), you can see the consequences
|
665 |
+
// of sharing if you call geometry.applyMatrix4( matrix ).
|
666 |
+
if ( notDegenerate( v1, v2, v3 ) ) {
|
667 |
+
|
668 |
+
indices[ indexCount ++ ] = v1;
|
669 |
+
indices[ indexCount ++ ] = v2;
|
670 |
+
indices[ indexCount ++ ] = v3;
|
671 |
+
|
672 |
+
}
|
673 |
+
|
674 |
+
if ( notDegenerate( v1, v3, v4 ) ) {
|
675 |
+
|
676 |
+
indices[ indexCount ++ ] = v1;
|
677 |
+
indices[ indexCount ++ ] = v3;
|
678 |
+
indices[ indexCount ++ ] = v4;
|
679 |
+
|
680 |
+
}
|
681 |
+
|
682 |
+
}
|
683 |
+
|
684 |
+
}
|
685 |
+
|
686 |
+
// increment only if a surface was used
|
687 |
+
surfCount ++;
|
688 |
+
|
689 |
+
}
|
690 |
+
|
691 |
+
}
|
692 |
+
|
693 |
+
this.setIndex( new BufferAttribute( indices, 1 ) );
|
694 |
+
this.setAttribute( 'position', new BufferAttribute( vertices, 3 ) );
|
695 |
+
this.setAttribute( 'normal', new BufferAttribute( normals, 3 ) );
|
696 |
+
this.setAttribute( 'uv', new BufferAttribute( uvs, 2 ) );
|
697 |
+
|
698 |
+
this.computeBoundingSphere();
|
699 |
+
|
700 |
+
}
|
701 |
+
|
702 |
+
}
|
703 |
+
|
704 |
+
export { TeapotGeometry };
|
libs/three.js/0.172.0/jsm/geometries/TextGeometry.js
ADDED
@@ -0,0 +1,54 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
/**
|
2 |
+
* Text = 3D Text
|
3 |
+
*
|
4 |
+
* parameters = {
|
5 |
+
* font: <THREE.Font>, // font
|
6 |
+
*
|
7 |
+
* size: <float>, // size of the text
|
8 |
+
* depth: <float>, // thickness to extrude text
|
9 |
+
* curveSegments: <int>, // number of points on the curves
|
10 |
+
*
|
11 |
+
* bevelEnabled: <bool>, // turn on bevel
|
12 |
+
* bevelThickness: <float>, // how deep into text bevel goes
|
13 |
+
* bevelSize: <float>, // how far from text outline (including bevelOffset) is bevel
|
14 |
+
* bevelOffset: <float> // how far from text outline does bevel start
|
15 |
+
* }
|
16 |
+
*/
|
17 |
+
|
18 |
+
import {
|
19 |
+
ExtrudeGeometry
|
20 |
+
} from 'three';
|
21 |
+
|
22 |
+
class TextGeometry extends ExtrudeGeometry {
|
23 |
+
|
24 |
+
constructor( text, parameters = {} ) {
|
25 |
+
|
26 |
+
const font = parameters.font;
|
27 |
+
|
28 |
+
if ( font === undefined ) {
|
29 |
+
|
30 |
+
super(); // generate default extrude geometry
|
31 |
+
|
32 |
+
} else {
|
33 |
+
|
34 |
+
const shapes = font.generateShapes( text, parameters.size );
|
35 |
+
|
36 |
+
// defaults
|
37 |
+
|
38 |
+
if ( parameters.depth === undefined ) parameters.depth = 50;
|
39 |
+
if ( parameters.bevelThickness === undefined ) parameters.bevelThickness = 10;
|
40 |
+
if ( parameters.bevelSize === undefined ) parameters.bevelSize = 8;
|
41 |
+
if ( parameters.bevelEnabled === undefined ) parameters.bevelEnabled = false;
|
42 |
+
|
43 |
+
super( shapes, parameters );
|
44 |
+
|
45 |
+
}
|
46 |
+
|
47 |
+
this.type = 'TextGeometry';
|
48 |
+
|
49 |
+
}
|
50 |
+
|
51 |
+
}
|
52 |
+
|
53 |
+
|
54 |
+
export { TextGeometry };
|