@ccp-nc/crystvis-js 0.4.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.json +16 -0
- package/.github/workflows/test-mocha.yml +30 -0
- package/.vscode/settings.json +4 -0
- package/LICENSE +21 -0
- package/README.html +1127 -0
- package/README.md +76 -0
- package/demo/demo.css +30 -0
- package/demo/index.html +76 -0
- package/demo/main.js +143 -0
- package/docs/.nojekyll +0 -0
- package/docs-tutorials/Events.md +57 -0
- package/docs-tutorials/Queries.md +50 -0
- package/fonts/Rubik/OFL.txt +93 -0
- package/fonts/Rubik/README.txt +77 -0
- package/fonts/Rubik/Rubik-Italic-VariableFont_wght.ttf +0 -0
- package/fonts/Rubik/Rubik-VariableFont_wght.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Black.ttf +0 -0
- package/fonts/Rubik/static/Rubik-BlackItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Bold.ttf +0 -0
- package/fonts/Rubik/static/Rubik-BoldItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-ExtraBold.ttf +0 -0
- package/fonts/Rubik/static/Rubik-ExtraBoldItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Italic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Light.ttf +0 -0
- package/fonts/Rubik/static/Rubik-LightItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Medium.ttf +0 -0
- package/fonts/Rubik/static/Rubik-MediumItalic.ttf +0 -0
- package/fonts/Rubik/static/Rubik-Regular.ttf +0 -0
- package/fonts/Rubik/static/Rubik-SemiBold.ttf +0 -0
- package/fonts/Rubik/static/Rubik-SemiBoldItalic.ttf +0 -0
- package/index.html +25 -0
- package/index.js +11 -0
- package/jsconf.json +14 -0
- package/lib/assets/fonts/Rubik-Medium.fnt +297 -0
- package/lib/assets/fonts/Rubik-Medium.png +0 -0
- package/lib/assets/fonts/bmpfonts.in.js +16 -0
- package/lib/assets/fonts/bmpfonts.js +9 -0
- package/lib/assets/fonts/font.js +82 -0
- package/lib/assets/fonts/index.js +14 -0
- package/lib/assets/fonts/threebmfont.js +28 -0
- package/lib/data.js +125 -0
- package/lib/formats/cell.js +114 -0
- package/lib/formats/cif.js +22 -0
- package/lib/formats/magres.js +337 -0
- package/lib/formats/xyz.js +124 -0
- package/lib/loader.js +87 -0
- package/lib/model.js +2076 -0
- package/lib/modelview.js +382 -0
- package/lib/nmrdata.js +2898 -0
- package/lib/orbit.js +1233 -0
- package/lib/primitives/atoms.js +261 -0
- package/lib/primitives/cell.js +160 -0
- package/lib/primitives/dither.js +156 -0
- package/lib/primitives/ellipsoid.js +183 -0
- package/lib/primitives/geometries.js +20 -0
- package/lib/primitives/index.js +48 -0
- package/lib/primitives/isosurface.js +171 -0
- package/lib/primitives/shapes.js +100 -0
- package/lib/primitives/sprites.js +172 -0
- package/lib/query.js +158 -0
- package/lib/render.js +440 -0
- package/lib/selbox.js +361 -0
- package/lib/shaders/aura.frag +26 -0
- package/lib/shaders/aura.vert +37 -0
- package/lib/shaders/dither.frag +42 -0
- package/lib/shaders/dither.vert +8 -0
- package/lib/shaders/index.in.js +17 -0
- package/lib/shaders/index.js +25 -0
- package/lib/shaders/msdf300.frag +25 -0
- package/lib/shaders/msdf300.vert +45 -0
- package/lib/tensor.js +227 -0
- package/lib/utils.js +168 -0
- package/lib/visualizer.js +480 -0
- package/package.json +106 -0
- package/scripts/build-bundle.js +17 -0
- package/scripts/build-fonts.js +43 -0
- package/scripts/build-resources.js +46 -0
- package/scripts/plugins-shim.js +10 -0
- package/test/chemdata.js +69 -0
- package/test/data/CHA.cif +74 -0
- package/test/data/H2O.xyz +8 -0
- package/test/data/H2_bound.xyz +4 -0
- package/test/data/ethanol.cell +25 -0
- package/test/data/ethanol.magres +238 -0
- package/test/data/example_single.cif +789 -0
- package/test/data/frac.cell +8 -0
- package/test/data/org.cif +427 -0
- package/test/data/pyridine.xyz +13 -0
- package/test/data/si8.xyz +10 -0
- package/test/loader.js +107 -0
- package/test/model.js +368 -0
- package/test/query.js +135 -0
- package/test/tensor.js +133 -0
- package/test/test-html/examples.js +1485 -0
- package/test/test-html/index.html +33 -0
- package/test/test-html/index.js +279 -0
- package/tools/compile_colors.py +120 -0
- package/tools/compile_periodic.py +96 -0
- package/tools/ptable.json +497 -0
- package/tools/test +5844 -0
package/lib/query.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Utility functions for queries across Models
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import _ from 'lodash';
|
|
9
|
+
|
|
10
|
+
class QueryParser {
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* A parser for queries of any sort; it will recognize all queries whose names
|
|
14
|
+
* are contained in 'methods', which optionally can belong to context;
|
|
15
|
+
* will also recognize logical operators $and, $or and $xor.
|
|
16
|
+
* @class
|
|
17
|
+
* @param {Object} methods Dictionary of methods to be used for querying.
|
|
18
|
+
* @param {*} context Context for the methods above (namely, parent instance if they're class methods)
|
|
19
|
+
* @param {Function} comparison Function used to compare two elements and check that they're identical. Must take
|
|
20
|
+
* (x1, x2) as argument and return a bool.
|
|
21
|
+
*/
|
|
22
|
+
constructor(methods, context, comparison) {
|
|
23
|
+
|
|
24
|
+
this._methods = methods;
|
|
25
|
+
|
|
26
|
+
this._context = context;
|
|
27
|
+
this._comparison = comparison || function(x1, x2) {
|
|
28
|
+
return x1 == x2;
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Parses and applies a query. The query comes in the form of a list, like:
|
|
34
|
+
*
|
|
35
|
+
* ['name', '<argument1>', '<argument2>', ...]
|
|
36
|
+
*
|
|
37
|
+
* where 'name' has to be contained in the methods table, and return a list of
|
|
38
|
+
* results. Logical operators can be used, for example:
|
|
39
|
+
*
|
|
40
|
+
* ['$and', [query1], [query2], ...]
|
|
41
|
+
*
|
|
42
|
+
* will return the intersection of the various queries. The logical operators
|
|
43
|
+
* are:
|
|
44
|
+
*
|
|
45
|
+
* - $and : intersection of the results of the arguments
|
|
46
|
+
* - $or : sum of the results of the arguments
|
|
47
|
+
* - $xor : sum minus intersection of the results of the arguments
|
|
48
|
+
*
|
|
49
|
+
* @param {Array} query The query to traverse
|
|
50
|
+
*
|
|
51
|
+
* @returns {Array} The outcome of the query
|
|
52
|
+
*/
|
|
53
|
+
parse(query) {
|
|
54
|
+
|
|
55
|
+
var names = Object.keys(query);
|
|
56
|
+
|
|
57
|
+
switch (names.length) {
|
|
58
|
+
case 0:
|
|
59
|
+
return [];
|
|
60
|
+
case 1:
|
|
61
|
+
let name = names[0];
|
|
62
|
+
let args = query[name];
|
|
63
|
+
|
|
64
|
+
if (!(args instanceof Array)) {
|
|
65
|
+
args = [args]; // Just for a single argument
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (name in this._methods) {
|
|
69
|
+
// Then call the method
|
|
70
|
+
return this._methods[name].apply(this._context, args);
|
|
71
|
+
} else switch (name) {
|
|
72
|
+
case '$and':
|
|
73
|
+
return this.and(args);
|
|
74
|
+
case '$or':
|
|
75
|
+
return this.or(args);
|
|
76
|
+
case '$xor':
|
|
77
|
+
return this.xor(args);
|
|
78
|
+
default:
|
|
79
|
+
throw new Error('Invalid query: query name not recognized');
|
|
80
|
+
}
|
|
81
|
+
default:
|
|
82
|
+
let queries = _.map(query, (v, k) => {
|
|
83
|
+
var q = {};
|
|
84
|
+
q[k] = v;
|
|
85
|
+
return q;
|
|
86
|
+
});
|
|
87
|
+
return this.and(queries);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
for (let i = 0; i < names.length; ++i) {
|
|
92
|
+
let name = names[i];
|
|
93
|
+
let args = query[name];
|
|
94
|
+
|
|
95
|
+
if (!(args instanceof Array)) {
|
|
96
|
+
args = [args]; // Just for a single argument
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
if (name in this._methods) {
|
|
100
|
+
// Then call the method
|
|
101
|
+
return this._methods[name].apply(this._context, args);
|
|
102
|
+
} else switch (name) {
|
|
103
|
+
case '$and':
|
|
104
|
+
return this.and(args);
|
|
105
|
+
case '$or':
|
|
106
|
+
return this.or(args);
|
|
107
|
+
case '$xor':
|
|
108
|
+
return this.xor(args);
|
|
109
|
+
default:
|
|
110
|
+
throw new Error('Invalid query: query name not recognized');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
var name = query[0];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
and(queries) {
|
|
119
|
+
var that = this;
|
|
120
|
+
var results = _.map(queries, function(q) {
|
|
121
|
+
return that.parse(q)
|
|
122
|
+
});
|
|
123
|
+
var result = _.reduce(results.slice(1), function(r, v) {
|
|
124
|
+
return _.intersectionWith(r, v, that._comparison);
|
|
125
|
+
}, results[0]);
|
|
126
|
+
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
or(queries) {
|
|
131
|
+
var that = this;
|
|
132
|
+
var results = _.map(queries, function(q) {
|
|
133
|
+
return that.parse(q)
|
|
134
|
+
});
|
|
135
|
+
var result = _.reduce(results, function(r, v) {
|
|
136
|
+
return _.unionWith(r, v, that._comparison);
|
|
137
|
+
}, []);
|
|
138
|
+
|
|
139
|
+
return result;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
xor(queries) {
|
|
143
|
+
var that = this;
|
|
144
|
+
var results = _.map(queries, function(q) {
|
|
145
|
+
return that.parse(q)
|
|
146
|
+
});
|
|
147
|
+
var result = _.reduce(results, function(r, v) {
|
|
148
|
+
return _.xorWith(r, v, that._comparison);
|
|
149
|
+
}, []);
|
|
150
|
+
|
|
151
|
+
return result;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export {
|
|
157
|
+
QueryParser
|
|
158
|
+
}
|
package/lib/render.js
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Classes and methods for rendering using THREE.js
|
|
5
|
+
* @module
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// NPM imports
|
|
9
|
+
import $ from 'jquery';
|
|
10
|
+
import _ from 'lodash';
|
|
11
|
+
import * as THREE from 'three';
|
|
12
|
+
|
|
13
|
+
// Internal imports
|
|
14
|
+
import {
|
|
15
|
+
OrbitControls
|
|
16
|
+
} from './orbit.js';
|
|
17
|
+
import {
|
|
18
|
+
SelectionBox,
|
|
19
|
+
SelectionHelper
|
|
20
|
+
} from './selbox.js';
|
|
21
|
+
|
|
22
|
+
import * as Primitives from './primitives/index.js';
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class Renderer {
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* An object representing the THREE.js graphical renderer for atomic models
|
|
29
|
+
* @class
|
|
30
|
+
* @param {string} target CSS selector for the target HTML element in which to put the renderer
|
|
31
|
+
* @param {int} width Desired width for the renderer
|
|
32
|
+
* @param {int} height Desired height for the renderer. If both this and width are zero, automatically
|
|
33
|
+
* resizes with the container
|
|
34
|
+
*/
|
|
35
|
+
constructor(target, width, height) {
|
|
36
|
+
|
|
37
|
+
// Grab the target element
|
|
38
|
+
this._div = $(target);
|
|
39
|
+
this._autoResize = (width == 0) && (height == 0);
|
|
40
|
+
|
|
41
|
+
this._w = width;
|
|
42
|
+
this._h = height;
|
|
43
|
+
this._updateSize();
|
|
44
|
+
|
|
45
|
+
// Renderer
|
|
46
|
+
this._r = new THREE.WebGLRenderer();
|
|
47
|
+
this._r.autoClear = true;
|
|
48
|
+
this._r.setPixelRatio(window.devicePixelRatio);
|
|
49
|
+
this._r.setSize(this._w, this._h);
|
|
50
|
+
this._div.append(this._r.domElement);
|
|
51
|
+
|
|
52
|
+
// Scene
|
|
53
|
+
this._s = new THREE.Scene();
|
|
54
|
+
|
|
55
|
+
// Camera
|
|
56
|
+
var ratio = this._w * 1.0 / this._h;
|
|
57
|
+
this._depth = 1000; // For now we use a constant
|
|
58
|
+
this._c = new THREE.OrthographicCamera(-20, 20,
|
|
59
|
+
20 / ratio, -20 / ratio,
|
|
60
|
+
0.1, 2 * this._depth);
|
|
61
|
+
this._s.add(this._c);
|
|
62
|
+
|
|
63
|
+
// Lights
|
|
64
|
+
this._l = {}
|
|
65
|
+
this._l._amb = new THREE.AmbientLight(0xffffff, 0.2);
|
|
66
|
+
this._l._amb.name = 'ambLight';
|
|
67
|
+
this._l._dir = new THREE.DirectionalLight(0xffffff, 0.5);
|
|
68
|
+
this._l._dir.position.set(0, 1, -1);
|
|
69
|
+
this._l._dir.name = 'dirLight';
|
|
70
|
+
this._s.add(this._l._amb); // Added to scene
|
|
71
|
+
this._c.add(this._l._dir); // Added to camera (rotates with it)
|
|
72
|
+
|
|
73
|
+
// Controls
|
|
74
|
+
this._oc = new OrbitControls(this._c, this._r.domElement);
|
|
75
|
+
this.resetOrbitCenter(0, 0, 0);
|
|
76
|
+
|
|
77
|
+
// Raycast for clicks
|
|
78
|
+
this._rcastlist = [];
|
|
79
|
+
this._r.domElement.addEventListener('pointerdown', this._raycastClick.bind(this));
|
|
80
|
+
|
|
81
|
+
// Groups
|
|
82
|
+
this._groups = {
|
|
83
|
+
model: new THREE.Group(),
|
|
84
|
+
primitives: new THREE.Group()
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
this._s.add(this._groups.model);
|
|
88
|
+
this._s.add(this._groups.primitives);
|
|
89
|
+
|
|
90
|
+
// Selection box (multiple raycast)
|
|
91
|
+
this._sboxlist = [];
|
|
92
|
+
this._sbox = new SelectionBox(this._c, this._s);
|
|
93
|
+
this._sboxhelp = new SelectionHelper(this._sbox, this, 'crystvis-selbox-helper');
|
|
94
|
+
this._sboxhelp.selectOverCallback = this._selectBoxEnd.bind(this);
|
|
95
|
+
|
|
96
|
+
// Color scheme
|
|
97
|
+
this._cell_line_color = 0xaaaaaa;
|
|
98
|
+
this._cell_x_color = 0xff0000;
|
|
99
|
+
this._cell_y_color = 0x00ff00;
|
|
100
|
+
this._cell_z_color = 0x0000ff;
|
|
101
|
+
|
|
102
|
+
this.selbox_bkg_color = 0x1111aa;
|
|
103
|
+
this.selbox_border_color = 0x5555dd;
|
|
104
|
+
this.selbox_opacity = 0.5;
|
|
105
|
+
|
|
106
|
+
// Set up the animation
|
|
107
|
+
this._animate();
|
|
108
|
+
|
|
109
|
+
// Resizing in case it's required
|
|
110
|
+
if (this._autoResize) {
|
|
111
|
+
this._resizeObs = new ResizeObserver(this._resize.bind(this)
|
|
112
|
+
);
|
|
113
|
+
this._resizeObs.observe(this._div[0]);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
_render() {
|
|
119
|
+
this._r.render(this._s, this._c);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
_animate() {
|
|
123
|
+
requestAnimationFrame(this._animate.bind(this));
|
|
124
|
+
this._render();
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
_updateSize() {
|
|
128
|
+
this._w = this._w || this._div.innerWidth();
|
|
129
|
+
this._h = this._h || this._div.innerHeight();
|
|
130
|
+
this._offset = this._div.offset();
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
_resize() {
|
|
134
|
+
// Resize event handler
|
|
135
|
+
this._w = 0;
|
|
136
|
+
this._h = 0;
|
|
137
|
+
this._updateSize()
|
|
138
|
+
this._r.setSize(this._w, this._h);
|
|
139
|
+
|
|
140
|
+
var ratio = this._w * 1.0 / this._h;
|
|
141
|
+
this._c.top = 20/ratio;
|
|
142
|
+
this._c.bottom = -20/ratio;
|
|
143
|
+
this._c.updateProjectionMatrix();
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
_raycastClick(e) {
|
|
147
|
+
|
|
148
|
+
// We create a 2D vector
|
|
149
|
+
var vector = this.documentToWorld(e.clientX, e.clientY);
|
|
150
|
+
|
|
151
|
+
// We create a raycaster, which is some kind of laser going through your scene
|
|
152
|
+
var raycaster = new THREE.Raycaster();
|
|
153
|
+
// We apply two parameters to the 'laser', its origin (where the user clicked)
|
|
154
|
+
// and the direction (what the camera 'sees')
|
|
155
|
+
raycaster.setFromCamera(vector, this._c);
|
|
156
|
+
|
|
157
|
+
// We get all the objects the 'laser' find on its way (it returns an array containing the objects)
|
|
158
|
+
for (var i = 0; i < this._rcastlist.length; ++i) {
|
|
159
|
+
|
|
160
|
+
var func = this._rcastlist[i][0];
|
|
161
|
+
var targ = this._rcastlist[i][1];
|
|
162
|
+
var filter = this._rcastlist[i][2];
|
|
163
|
+
|
|
164
|
+
targ = targ || this._s;
|
|
165
|
+
var intersects = raycaster.intersectObjects(targ.children);
|
|
166
|
+
|
|
167
|
+
var objects = [];
|
|
168
|
+
for (var j = 0; j < intersects.length; ++j) {
|
|
169
|
+
var o = intersects[j].object;
|
|
170
|
+
if (!filter || intersects[j].object instanceof filter) {
|
|
171
|
+
objects.push(o);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
func(objects, e);
|
|
176
|
+
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_selectBoxEnd(p1, p2) {
|
|
181
|
+
for (var i = 0; i < this._sboxlist.length; ++i) {
|
|
182
|
+
var func = this._sboxlist[i][0];
|
|
183
|
+
var targ = this._sboxlist[i][1];
|
|
184
|
+
var filter = this._sboxlist[i][2];
|
|
185
|
+
|
|
186
|
+
var selected = this._sbox.select(p1, p2, targ);
|
|
187
|
+
|
|
188
|
+
selected = _.filter(selected, function(o) {
|
|
189
|
+
return (!filter || o instanceof filter)
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
func(selected);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
add(object, group = 'primitives') {
|
|
197
|
+
if (!(this._groups[group].children.includes(object)))
|
|
198
|
+
this._groups[group].add(object);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
remove(object, group = 'primitives') {
|
|
202
|
+
this._groups[group].remove(object);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Add a listener for click events on a given group
|
|
207
|
+
*
|
|
208
|
+
* @param {Function} listener Listener function. Will receive a list of objects, sorted by distance.
|
|
209
|
+
* @param {THREE.Group} group Group on which to detect clicks
|
|
210
|
+
* @param {Function} filtertype If present, only pass objects of this class
|
|
211
|
+
*
|
|
212
|
+
* @returns {Array} Reference to the created listener
|
|
213
|
+
*/
|
|
214
|
+
addClickListener(listener, group, filtertype = null) {
|
|
215
|
+
var cl = [listener, group, filtertype];
|
|
216
|
+
this._rcastlist.push(cl);
|
|
217
|
+
return cl;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Remove a listener
|
|
222
|
+
* @param {Array} cl Reference to the listener to remove (as returned by addClickListener)
|
|
223
|
+
*/
|
|
224
|
+
removeClickListener(cl) {
|
|
225
|
+
_.pull(this._rcastlist, cl);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Add a listener for selection box events
|
|
230
|
+
*
|
|
231
|
+
* @param {Function} listener Listener function
|
|
232
|
+
* @param {THREE.Group} group Group on which to detect clicks
|
|
233
|
+
* @param {Function} filtertype If present, only pass objects of this class
|
|
234
|
+
*
|
|
235
|
+
* @returns {Array} Reference to the created listener
|
|
236
|
+
*/
|
|
237
|
+
addSelBoxListener(listener, group, filtertype = null) {
|
|
238
|
+
var sbl = [listener, group, filtertype];
|
|
239
|
+
this._sboxlist.push(sbl);
|
|
240
|
+
return sbl;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Remove a selection box listener
|
|
245
|
+
* @param {Array} sbl Reference to the listener to remove (as returned by addSelBoxListener)
|
|
246
|
+
*/
|
|
247
|
+
removeSelBoxListener(sbl) {
|
|
248
|
+
_.pull(this._sboxlist, sbl);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Set properties of ambient light
|
|
253
|
+
*
|
|
254
|
+
* @param {float} intensity Intensity
|
|
255
|
+
*/
|
|
256
|
+
setAmbientLight(intensity) {
|
|
257
|
+
this._l._amb.intensity = intensity;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Set properties of directional light
|
|
262
|
+
*
|
|
263
|
+
* @param {float} intensity Intensity
|
|
264
|
+
* @param {float} px Direction, x
|
|
265
|
+
* @param {float} py Direction, y
|
|
266
|
+
* @param {float} pz Direction, z
|
|
267
|
+
*/
|
|
268
|
+
setDirectionalLight(intensity, px, py, pz) {
|
|
269
|
+
px = px === null ? this._l.dir.position.x : px;
|
|
270
|
+
py = py === null ? this._l.dir.position.y : py;
|
|
271
|
+
pz = pz === null ? this._l.dir.position.z : pz;
|
|
272
|
+
this._l._dir.intensity = intensity;
|
|
273
|
+
this._l._dir.position.set(px, py, pz);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Reset the camera's view so its central axis is rendered
|
|
278
|
+
* with an offset from the center of the canvas
|
|
279
|
+
*
|
|
280
|
+
* @param {float} fx Horizontal offset in the canvas (in fraction of its width, -0.5 to 0.5)
|
|
281
|
+
* @param {float} fy Vertical offset in the canvas (in fraction of its height, -0.5 to 0.5)
|
|
282
|
+
*/
|
|
283
|
+
resetCameraCenter(fx=0, fy=0) {
|
|
284
|
+
this._c.setViewOffset(this._w, this._h, -fx*this._w, -fy*this._h,
|
|
285
|
+
this._w, this._h);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/**
|
|
289
|
+
* Reset the camera so it orbits around a given point
|
|
290
|
+
*
|
|
291
|
+
* @param {float} x X coordinate of the point
|
|
292
|
+
* @param {float} y Y coordinate of the point
|
|
293
|
+
* @param {float} z Z coordinate of the point
|
|
294
|
+
*/
|
|
295
|
+
resetOrbitCenter(x=0, y=0, z=0) {
|
|
296
|
+
|
|
297
|
+
var p = new THREE.Vector3(x, y, z);
|
|
298
|
+
|
|
299
|
+
this._c.position.set(x, y, this._depth + 0.1);
|
|
300
|
+
this._c.lookAt(p);
|
|
301
|
+
this._oc.target = p;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Remove all currently rendered objects.
|
|
306
|
+
*/
|
|
307
|
+
clear(model = true, primitives = true) {
|
|
308
|
+
if (model)
|
|
309
|
+
this._groups.model.clear();
|
|
310
|
+
if (primitives)
|
|
311
|
+
this._groups.primitives.clear();
|
|
312
|
+
// Reset camera position
|
|
313
|
+
this._oc.reset();
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Convert coordinates in the dom element frame to coordinates in the world frame
|
|
318
|
+
*/
|
|
319
|
+
documentToWorld(x, y) {
|
|
320
|
+
// We create a 2D vector
|
|
321
|
+
var vector = new THREE.Vector2();
|
|
322
|
+
// We set its position where the user clicked and we convert it to a number between -1 & 1
|
|
323
|
+
vector.set(
|
|
324
|
+
2 * ((x - this._offset.left) / this._w) - 1,
|
|
325
|
+
1 - 2 * ((y - this._offset.top) / this._h)
|
|
326
|
+
);
|
|
327
|
+
|
|
328
|
+
return vector;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Style
|
|
332
|
+
get selbox_bkg_color() {
|
|
333
|
+
return this._selbox_bkg_color;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
set selbox_bkg_color(c) {
|
|
337
|
+
c = new THREE.Color(c).getStyle();
|
|
338
|
+
this._selbox_bkg_color = c;
|
|
339
|
+
this._sboxhelp.element.css({
|
|
340
|
+
'background-color': c
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
get selbox_border_color() {
|
|
345
|
+
return this._selbox_border_color;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
set selbox_border_color(c) {
|
|
349
|
+
c = new THREE.Color(c).getStyle();
|
|
350
|
+
this._selbox_border_color = c;
|
|
351
|
+
this._sboxhelp.element.css({
|
|
352
|
+
'border-color': c
|
|
353
|
+
});
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
get selbox_opacity() {
|
|
357
|
+
return this._selbox_opacity;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
set selbox_opacity(o) {
|
|
361
|
+
this._selbox_opacity = o;
|
|
362
|
+
this._sboxhelp.element.css({
|
|
363
|
+
'opacity': o
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// This convenient wrapper is useful to keep the Primitives out of the Model's logic.
|
|
368
|
+
// This way, we don't suffer problems when testing purely mathematical/logical stuff
|
|
369
|
+
// by command line with mocha. Primitives are messy and don't build well for CLI with
|
|
370
|
+
// esbuild, for some reason.
|
|
371
|
+
get Primitives() {
|
|
372
|
+
return Primitives;
|
|
373
|
+
};
|
|
374
|
+
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Add a vector field representation to the model
|
|
379
|
+
*
|
|
380
|
+
* @param {Array} points List of origins for the vectors
|
|
381
|
+
* @param {Array} vectors Vectors to plot
|
|
382
|
+
* @param {*} colors Colors for each vector. Can be a single color, an array of colors, or a function that takes
|
|
383
|
+
* origin, vector, and index, and returns a color.
|
|
384
|
+
* @param {float} scale Scaling factor
|
|
385
|
+
*
|
|
386
|
+
* @returns {THREE.Group} Rendered object
|
|
387
|
+
*/
|
|
388
|
+
|
|
389
|
+
/*
|
|
390
|
+
_addVectorField: function(points, vectors, colors, scale) {
|
|
391
|
+
|
|
392
|
+
var N = points.length;
|
|
393
|
+
if (vectors.length != N)
|
|
394
|
+
throw 'Points and vectors arrays not matching for vector field';
|
|
395
|
+
|
|
396
|
+
// We always reduce colors to a function with signature (p, v, i) but
|
|
397
|
+
// it can also be an array or a single scalar.
|
|
398
|
+
colors = colors || 0xffffff;
|
|
399
|
+
switch (typeof colors) {
|
|
400
|
+
case 'function':
|
|
401
|
+
break;
|
|
402
|
+
case 'object':
|
|
403
|
+
if (colors.length != N)
|
|
404
|
+
throw 'Colors array not matching for vector field';
|
|
405
|
+
var colors_arr = colors.slice();
|
|
406
|
+
colors = function(p, v, i) {
|
|
407
|
+
return colors_arr[i];
|
|
408
|
+
}
|
|
409
|
+
break;
|
|
410
|
+
default:
|
|
411
|
+
var colors_val = colors;
|
|
412
|
+
colors = function(p, v, i) {
|
|
413
|
+
return colors_val;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
scale = scale || 1;
|
|
417
|
+
|
|
418
|
+
var vfield = new THREE.Group();
|
|
419
|
+
|
|
420
|
+
for (var i = 0; i < N; ++i) {
|
|
421
|
+
var p = points[i];
|
|
422
|
+
var v = vectors[i];
|
|
423
|
+
var c = colors(p, v, i);
|
|
424
|
+
var l = v.length();
|
|
425
|
+
v.normalize();
|
|
426
|
+
var arr = new THREE.ArrowHelper(v, p, l, c, 0.2 * l, 0.1 * l);
|
|
427
|
+
vfield.add(arr);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
this._g._plots.add(vfield);
|
|
431
|
+
|
|
432
|
+
return vfield;
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
*/
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
export {
|
|
439
|
+
Renderer
|
|
440
|
+
};
|