-
Notifications
You must be signed in to change notification settings - Fork 0
/
GrObject.js
250 lines (228 loc) · 7.39 KB
/
GrObject.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
/*jshint esversion: 11 */
// @ts-check
/**
* CS559 3D World Framework Code
*
* GrObject: a "thin wrapper" around Three.JS's Object3D to facilitate
* creating UIs and doing animation
*
* @module GrObject
*/
/* students will want to create objects that extend this class */
/*
* This is the main class for the framework. Most of the work involves extending
* the class `GrObject` defined here.
*/
import * as T from "../CS559-Three/build/three.module.js";
/**
* This function converts from the specifications given to the `GrObject`
* constructor into the form used internally. It is the best documentation for
* how those descriptions are interpreted.
*
* when creating an object, a parameter is defined by an array of up to
* 5 things
* name (string)
* min (number)
* max (number)
* initial value (number)
* step size for slider (number)
*
* **Note:** this function is for internal use, but it is exported to convince
* JSDoc to document it.
*
* @param {string|Array} param
*/
export function paramObjFromParam(param) {
const paramObj = {
name: "no name",
min: 0,
max: 1,
initial: 0,
step: 0
};
if (typeof param === "string") {
paramObj.name = param;
} else if (Array.isArray(param)) {
if (param.length > 0) {
paramObj.name = param[0];
}
if (param.length > 1) {
paramObj.min = param[1];
}
if (param.length > 2) {
paramObj.max = param[2];
}
if (param.length > 3) {
paramObj.initial = param[3];
}
if (param.length > 4) {
paramObj.step = param[4];
}
}
// make sure the initial value is legal
if (paramObj.initial < paramObj.min) {
paramObj.initial = paramObj.min;
}
if (paramObj.initial > paramObj.max) {
paramObj.initial = paramObj.max;
}
return paramObj;
}
/**
* @class GrObject
*
* GrObjects have:
* - a name - each object should have a unique name (like an id), but this is not
* enforced
* - parameters (these are things that the user may want to control with sliders)
* - geometry / "Object3D" - they kind of serve like three's groups
* note: animation should not update the parameters
*
* any new object should provide methods for:
* - construction - the constructor needs to call the base class constructor
* and provide the parameters and geometry
* - update - which takes an array of parameters and sets things accordingly
* - stepWorld - which moves the animation ahead a small amount
*
*
* and optionally
* - lookfrom/lookat
*
* Note that a `GrObject` does not add itself to the scene (other things take care
* of that). When the object is added to the world, it's THREE objects are added to
* the `Scene` (the THREE world container).
*
*
*/
export class GrObject {
/**
* The parameter list (if provided) should be either a string
* (with the name of the parameter) or an Array with the first
* value being a string (the name), and the remaining 4 values being
* numbers: min, max, initial value, and step size (all optional).
* @see paramObjFromParam
*
* @param {String} name - unique name for the object
* @param {THREE.Object3D | Array<THREE.Object3D>} objectOrObjects
* @param {Array<string|Array>} [paramInfo] - a list of the parameters for the object
*/
constructor(name, objectOrObjects, paramInfo) {
// simple declarations of defaults so we can easily identify members
/** @type {Array<THREE.Object3D>} */
this.objects = [];
/** @type {Array<Object>} */
this.params = [];
/** @type {String} */
this.name = name;
/** A flag for if this object is ridable - if so, it should be a specific THREE object to
* parent the object to */
/** @type {THREE.Object3D | undefined } */
this.rideable = undefined;
/** the unique ID is a number (non-zero) that comes from the world - set by GrWorld.add*/
/** @type {Number} */
this.id = 0;
/** a flag as to whether this object should be "highlighted" as special - use by the UI */
/** @type {Boolean} */
this.highlighted = false;
// set up the object list
if (Array.isArray(objectOrObjects)) {
// we were given a list - do a deep copy
const objList = this.objects; // deal with the non-lexical this
objectOrObjects.forEach(function(obj) {
objList.push(obj);
});
} else {
// if there is 1 object (there might be zero)
if (objectOrObjects) {
this.objects.push(objectOrObjects);
}
}
// set up the parameters
// we allow specifying parameters in many different ways
// we always convert to lightweight objects
if (paramInfo) {
// Totally OK to have none
const self = this;
paramInfo.forEach(function(param) {
// default values for the parameter in case we don't get any
const paramObj = paramObjFromParam(param);
self.params.push(paramObj);
});
}
}
// methods that must be over-ridden
/**
* Advance the object by an amount of time. Time only flows forward
* so use this to figure out how fast things should move.
* In theory, it is always a "step" (1/60th of a second)
* In the past, so many things were stochastic and only computed the
* delta, that this became the norm (if you need to accumulate time
* you can sum the delta)
* time of day is provided so you can make objects that change over the
* course of the day - it is a number between 0-24 (midnight->midnight)
* it does not necessarily change smoothly.
* Delta is intended to be in "milliseconds" - but it is scaled by the current
* "speed" (and will be zero if time is stopped).
* @param {number} delta
* @param {number} timeOfDay
*/
stepWorld(delta, timeOfDay) {
// by default (base class), does nothing
}
/**
* set the parameter values to new values
* this gets called when the sliders are moved
* @param {Array<Number>} paramValues
*/
update(paramValues) {}
/**
* return a plausible lookfrom/lookat pair to look at this object
* this makes a guess based on the bounding box, but an object may
* want to override to give a better view
*
* Returns an array of 6 numbers (lookfrom X,Y,Z, lookat X, Y, Z)
*
* @returns {Array<Number>}
*/
lookFromLookAt() {
const bbox = new T.Box3();
bbox.setFromObject(this.objects[0]);
const x = (bbox.max.x + bbox.min.x) / 2;
const y = (bbox.max.y + bbox.min.y) / 2;
const z = (bbox.max.z + bbox.min.z) / 2;
// make the box a little bigger to deal with think/small objects
const dx = bbox.max.x - x + 0.05;
const dy = bbox.max.y - y + 0.05;
const dz = bbox.max.z - z + 0.05;
const d = Math.max(dx, dy, dz);
const fx = x + d * 3;
const fy = y + d * 3;
const fz = z + d * 3;
return [fx, fy, fz, x, y, z];
}
/**
* helper method - set the scale of the objects
* note: this sets the scale of all the root level objects
* it doesn't consider what was already there
* also, it is only a uniform method
*
* @param {number} scale=1.0
* @param {number} sy=0
* @param {number} sz=0
*/
setScale(scale = 1.0, sy = 0, sz = 0) {
const syy = sy || scale;
const szz = sz || scale;
this.objects.forEach(e => e.scale.set(scale, syy, szz));
}
/**
* set the position of each (root level) object
*
* @param {number} x
* @param {number} y
* @param {number} z
*/
setPos(x = 0, y = 0, z = 0) {
this.objects.forEach(e => e.position.set(x,y,z));
}
}