import { Box3 } from '@uino/base-thing';
import { Utils } from '../common/Utils';
import { ObjectExpression } from '../common/ObjectExpression';
import { AsyncSelector } from './AsyncSelector';
import { StyleSelector } from './StyleSelector';
import { InheritSelector } from './InheritSelector';
const _styleSelector = new StyleSelector();
const _inheritSelector = new InheritSelector();
// #region ObjectExpression-Cache
let _expressions = new Map();
function _getExpression(condition, mode) {
let key = mode ? mode + ':' + condition : condition;
let expression = _expressions.get(key);
if (!expression) {
expression = new ObjectExpression(condition, mode);
_expressions.set(key, expression);
}
return expression;
}
// #endregion
// #region Private
function _selectObjectsByExpression(expression, objects, result) {
objects.forEach(object => {
if (expression.evaluate(object)) {
result.push(object);
}
});
}
function _selectObjectsByRegExpName(condition, objects, result) {
objects.forEach(object => {
if (!condition.test(object.name)) {
return;
}
result.push(object);
});
}
function _selectObjectsByFunction(condition, objects, result) {
objects.forEach(object => {
if (!condition(object)) {
return;
}
result.push(object);
});
}
function _findObjectsByExpression(expression, objects) {
for (let i = 0, l = objects.length; i < l; i++) {
let object = objects[i];
if (expression.evaluate(object)) {
return object;
}
}
return null;
}
function _findObjectsByRegExpName(condition, objects) {
for (let i = 0, l = objects.length; i < l; i++) {
let object = objects[i];
if (condition.test(object.name)) {
return object;
}
}
return null;
}
function _findObjectsByFunction(condition, objects) {
for (let i = 0, l = objects.length; i < l; i++) {
let object = objects[i];
if (condition(object)) {
return object;
}
}
return null;
}
// Get objects by condition.
function _getObjectsByCondition(condition, objects) {
if (condition) {
if (Utils.isString(condition)) {
return Selector.select(condition, objects);
}
else if (Utils.isArray(condition)) {
return condition;
}
else if (condition.isSelector) {
return condition.objects;
}
else {
return [condition];
}
}
return null;
}
function _parseObjects(param) {
if (Utils.isArray(param)) {
return param;
}
if (Utils.isObject(param)) {
return Utils.parseValue(param['objects'], []);
}
return [];
}
// #endregion
/**
* @class Selector
* The selector to find objects with some conditions.
* @memberof THING
* @public
*/
class Selector {
static asyncSelector = null;
constructor(param) {
this._app = Utils.getCurrentApp();
this._objects = _parseObjects(param);
// Hook [index] to implement accessor by index
let handler = {
get: (target, prop) => {
let object = this._objects[prop];
if (object && object.isBaseObject) {
return object;
}
return target[prop];
},
};
let proxy = new Proxy(this, handler);
Object.defineProperty(proxy, 'customFormatters', {
enumerable: false,
configurable: false,
get: () => {
return ['object', { object: this._objects }];
}
});
return proxy;
}
static update(deltaTime) {
ObjectExpression.update(deltaTime);
}
// #region Private
_queryInWorker(condition, root, recursive) {
return new Promise((resolve, reject) => {
// Query in normal async mode if it's just simple expression
if (condition == '*') {
let result;
if (root) {
result = root.query(condition, { recursive });
}
else {
result = this.onCreateInstance({ objects: this.objects });
}
resolve(result);
}
else {
let promise;
if (root) {
promise = Selector.asyncSelector.queryAsync(root, recursive, condition);
}
else {
promise = Selector.asyncSelector.selectAsync(this.objects, condition);
}
promise.then(result => {
let objects = this._app.objectManager.objects;
let filteredObjects = result.orderIDs.map(id => {
return objects.get(id);
});
let selector = this.onCreateInstance({ objects: filteredObjects });
resolve(selector);
});
}
});
}
// #endregion
/**
* Initialize with objects.
* @param {Array<THING.BaseObject>} objects The objects.
* @private
*/
init(objects) {
// Clear previous objects
this.clear();
// Set members as Array
this._objects = objects.slice(0);
return this;
}
applyObjects(funcName, args) {
this._objects.forEach(object => {
object[funcName].apply(object, args);
});
}
onCreateInstance() {
return new Selector(arguments[0]);
}
// #region Objects Query Operations
/**
* Select objects by condition with object's queryable state.
* @param {String} condition The conditions.
* @param {Array<Object>} objects The objects what to be queried.
* @returns {Array<Object>}
* @example
* // Select entities from objects
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entities = selector.select('.Entity', objects);
* let entity = entities[0];
* let ret = entity.isEntity;
* // @expect(ret == true)
*/
select(condition, objects) {
let result = [];
objects = objects || this.objects;
if (objects.length) {
if (Utils.isString(condition)) {
// All
if (condition == '*') {
result = objects.slice(0);
}
else {
let expression = _getExpression(condition);
_selectObjectsByExpression(expression, objects, result);
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
_selectObjectsByRegExpName(condition, objects, result);
}
// Function
else if (Utils.isFunction(condition)) {
_selectObjectsByFunction(condition, objects, result);
}
}
return result;
}
_selectByCondition(condition, objects, mode) {
let result = [];
objects = objects || this.objects;
if (objects.length) {
if (Utils.isString(condition)) {
let expression = _getExpression(condition, mode);
_selectObjectsByExpression(expression, objects, result);
}
}
return result;
}
_outputLogger(funcName, condition) {
let msg = { "condition": condition };
Utils.logger.api("Selector_" + funcName, { msg: JSON.stringify(msg) });
}
/**
* Test the single object with some conditions (without object's queryable state).
* @param {String} condition The conditions.
* @param {THING.BaseObject} object The object what to be queried.
* @returns {Boolean}
* @example
* // Test whether object fit specified condition
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let object = new THING.Entity();
* let ret = selector.test('.Entity', object);
* // @expect(ret == true);
* let box = new THING.Box();
* ret = selector.test('.Entity', box);
* // @expect(ret == false);
*/
test(condition, object) {
if (Utils.isString(condition)) {
// All
if (condition == '*') {
return true;
}
else {
let expression = _getExpression(condition);
return expression.evaluate(object);
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
return condition.test(object.name);
}
// Function
else if (Utils.isFunction(condition)) {
return condition(object);
}
return false;
}
/**
* Find objects with some conditions and store it in result.
* @param {String} condition The conditions.
* @returns {THING.Selector} The reference of target or new selector.
* @example
* // Query/select objects by specified condition
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entitys = selector.query('.Entity');
* let entity = entitys[0];
* // @expect(entity.isEntity == true);
*/
query(condition, objects, target) {
let objs = objects || this._objects;
let result = this.select(condition, objs).filter(object => {
return object.queryable;
});
target = target || this.onCreateInstance();
if (_NEED_LOGGER) {
this._outputLogger('query', condition.toString());
}
return target.init(result);
}
_queryByAttribute(objects, cb, target) {
var objs = (objects || this._objects).filter((c) => {
return cb(c) && c.queryable;
});
target = target || this.onCreateInstance();
return target.init(objs);
}
_queryByCondition(condition, objects, target, mode) {
let objs = objects || this._objects;
let result = this._selectByCondition(condition, objs, mode).filter(object => {
return object.queryable;
});
target = target || this.onCreateInstance();
return target.init(result);
}
/**
* Query children by reg exp.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByRegExp(/car/);
* let ret = car.length != 0;
* // @expect(ret == true)
* @public
*/
queryByRegExp(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByRegExp', condition.toString());
}
return this.query(condition, objects, target);
}
/**
* Query children by reg.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let car = selector.queryByReg(/car/);
* console.log(car);
* @deprecated 2.7
* @private
*/
queryByReg(condition, objects, target) {
return this.queryByRegExp(condition, objects, target);
}
/**
* Query children by tag.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByTags('Entity')
* let ret = car.length != 0;
* // @expect(ret == true)
* @public
*/
queryByTags(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByTags', condition);
}
return this._queryByCondition('tags(' + condition + ')', objects, target, 'tags');
}
/**
* Query children by name.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByName('car1');
* let ret = car.length != 0 && car[0].name == 'car1';
* // @expect(ret == true)
* @public
*/
queryByName(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByName', condition);
}
return this._queryByAttribute(objects, (c) => { return c.name == condition }, target);
}
/**
* Query children by uuid.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByUUID('1605');
* let ret = car.length != 0 && car[0].uuid == '1605';
* // @expect(ret == true)
* @public
*/
queryByUUID(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByUUID', condition);
}
return this._queryByAttribute(objects, (c) => { return c.uuid == condition }, target);
}
/**
* Query children by id.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryById('car01');
* let ret = car.length != 0 && car[0].id == 'car01';
* // @expect(ret == true)
* @public
*/
queryById(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryById', condition);
}
return this._queryByAttribute(objects, (c) => { return c.id == condition }, target);
}
/**
* Query children by type.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entitys = selector.queryByType('Entity');
* let ret = entitys.length != 0 && entitys[0].type == 'Entity';
* // @expect(ret == true)
* @public
*/
queryByType(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByType', condition);
}
var key = "is" + condition;
return this._queryByAttribute(objects, (c) => { return c[key] == true}, target);
}
/**
* Query children by userData.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let car = selector.queryByUserData('id="666"')
* let ret = car.length != 0 && car[0].userData.id == '666';
* // @expect(ret == true)
* @public
*/
queryByUserData(condition, objects, target) {
if (_NEED_LOGGER) {
this._outputLogger('queryByUserData', condition);
}
return this._queryByCondition(condition, objects, target, 'userData');
}
/**
* Query children by userData.
* @param {String} condition The condition to select objects.
* @param {Object} options The options.
* @returns {THING.Selector}
* @example
* let car = selector.queryByData('test=1')
* console.log(car);
* @deprecated 2.7
* @private
*/
queryByData(condition, objects, target) {
return this.queryByUserData(condition, objects, target);
}
/**
* Find objects with some conditions and store it in result in async mode.
* @param {String} condition The conditions.
* @param {THING.BaseObject} root The root object.
* @param {Boolean} recursive True indicates query in recursive mode.
* @returns {Promise<THING.Selector>}
* @example
* // Query/select objects by specified condition from root object in async mode
* selector.queryAsync('.Entity', rootObject, true).then((result) => {
* console.log(result);
* });
* @private
*/
queryAsync(condition, root, recursive) {
// Try to query in worker by async selector
if (Selector.asyncSelector) {
return this._queryInWorker(condition, root, recursive);
}
else {
return new Promise((resolve, reject) => {
let result;
if (root) {
result = root.query(condition, { recursive });
}
else {
result = this.query(condition);
}
resolve(result);
});
}
}
/**
* Find objects with some conditions and returns the first one.
* @param {String} condition The conditions.
* @param {Array<Object>} objects The objects what to be queried.
* @returns {BaseObject}
* @example
* let objects = app.query('.BaseObject');
* let selector = new THING.Selector(objects);
* let entity = selector.find('.Entity')
* let ret = entity instanceof THING.Entity;;
* // @expect(ret == true)
* @public
*/
find(condition, objects) {
let objs = objects || this._objects;
if (!objs.length) {
return null;
}
if (Utils.isString(condition)) {
// All
if (condition == '*') {
return objs[0];
}
else {
let expression = _getExpression(condition);
return _findObjectsByExpression(expression, objs);
}
}
// Reg Expression
else if (Utils.isRegExp(condition)) {
return _findObjectsByRegExpName(condition, objs);
}
// Function
else if (Utils.isFunction(condition)) {
return _findObjectsByFunction(condition, objs);
}
return null;
}
// #endregion
// #region Objects Array Operations
/**
* Push object into it.
* @param {THING.BaseObject|Array<THING.BaseObject>|THING.Selector} object The object what you want to push.
* @returns {Number} The length of objects after push.
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* let count1 = selector.length;
* let object = new THING.BaseObject();
* let count2 = selector.push(object);
* let ret = count2 - count1 == 1;
* // @expect(ret == true)
* @public
*/
push(object) {
if (object) {
if (object.isSelector) {
this._objects = this._objects.concat(object.objects);
}
else if (Utils.isArray(object)) {
this._objects = this._objects.concat(object);
}
else {
this._objects.push(object);
}
}
return this._objects.length;
}
/**
* Add objects by selector and return new one with results.
* @param {THING.Selector|Array<THING.BaseObject>|THING.BaseObject} selector The selector or object(s) to add.
* @returns {THING.Selector}
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* let count1 = selector.length;
* let object1 = new THING.BaseObject();
* let object2 = new THING.BaseObject();
* selector.add([object1,object2]);
* let count2 = selector.length;
* let ret = count2 - count1 == 2;
* // @expect(ret == true)
* @public
*/
add(selector) {
// Get source objects
let srcObjects = [];
if (selector.isSelector) {
srcObjects = selector.objects;
}
else if (Utils.isArray(selector)) {
srcObjects = selector;
}
else if (selector.isBaseObject) {
srcObjects = [selector];
}
// Add objects
let objects = this._objects.slice(0);
for (let i = 0; i < srcObjects.length; i++) {
let index = objects.indexOf(srcObjects[i]);
if (index === -1) {
objects.push(srcObjects[i]);
}
}
return this.onCreateInstance({ objects });
}
/**
* Remove objects with some conditions and return new selector with results.
* @param {String} condition The condition.
* @returns {THING.Selector}
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* selector.remove('.Entity');
* let count = selector.length;
* let ret = count == 0;
* // @expect(ret == true)
* @public
*/
remove(condition) {
let candidates = _getObjectsByCondition(condition, this._objects);
if (!candidates) {
return this.onCreateInstance({ objects: this.objects });
}
// Remove objects
let objects = this._objects.slice(0);
for (let i = 0; i < candidates.length; i++) {
let index = objects.indexOf(candidates[i]);
if (index !== -1) {
objects._removeAt(index);
}
}
return this.onCreateInstance({ objects });
}
/**
* Remove objects with some conditions and return new selector with results.
* @param {String} condition The condition.
* @returns {THING.Selector}
* @example
* // Return new selector by removing selector/objects
* let objects = selector.not([obj1, obj2, obj3]);
* console.log(objects);
* @public
*/
not(condition) {
return this.remove(condition);
}
/**
* Clear objects.
* @example
* let objects = app.query('.Entity');
* let selector = new THING.Selector(objects);
* selector.clear();
* let ret = count == 0;
* // @expect(ret == true)
* @public
*/
clear() {
this._objects.length = 0;
}
/**
* The function to call when to get objects by forEach.
* @callback OnTraverseObjectsInSelector
* @param {THING.BaseObject} object The object.
* @param {Number} index The index of objects.
* @param {Array<THING.BaseObject>} objects The objects.
*/
/**
* Traverse objects.
* @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
* @example
* // Traverse all objects in selector
* selector.forEach((object) => {
* console.log(object);
* });
* @public
*/
forEach(callback) {
let objects = this._objects;
for (let i = 0, l = objects.length; i < l; i++) {
callback(objects[i], i, objects);
}
}
/**
* Traverse objects in async mode.
* @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
* @example
* // Objects fly one by one(it would wait for previous object fly to complete before start)
* selector.forEachAsync(object => {
* return THING.App.current.camera.flyToAsync({
* target: object,
* duration: 1 * 1000,
* distance: 10,
* delayTime: THING.Math.randomFloat(0, 1000),
* complete: function (ev) {
* }
* });
* });
* @public
*/
forEachAsync(callback) {
this._objects.forEachAsync(callback);
}
/**
* Traverse objects in async mode.
* @param {OnTraverseObjectsInSelector} callback The callback function(returns false indicates break it, otherwise continue to process it).
* @deprecated 2.7
* @private
*/
forEachSync(callback) {
this.forEachAsync(callback);
}
/**
* Traverse self and all children.
* @param {Function} callback The callback function.
* @example
* selector.traverse(object =>{
* console.log(object)
* });
* @private
*/
traverse(callback) {
var objects = this._objects;
for (let i = 0, l = objects.length; i < l; i++) {
objects[i].traverse(callback);
}
}
/**
* Traverse self and all children. (Support for exit at traverse runtime)
* @param {Function} callback The callback function. (Return false to exit)
* @example
* selector.traverseBranch(object =>{
* if(object.children.length){
* return false;
* }
* return true;
* })
* @private
*/
traverseBranch(callback) {
for (let i = 0, l = this._objects.length; i < l; i++) {
const isBranch = this._objects[i].traverseBranch(callback);
if (!isBranch) {
return;
}
}
}
/**
* unique the selector. process the repeat and inherit object
* @private
* @example
* selector.uniqueObjects();
*/
unique() {
this._objects = Utils.uniqueObjects(this._objects);
}
/**
* The function to call when to get objects by map.
* @callback OnMapObjectsInSelector
* @param {THING.BaseObject} object The object.
* @param {Number} index The index of objects.
* @param {Array<THING.BaseObject>} objects The objects.
* @returns {THING.BaseObject}
*/
/**
* Traverse objects and return an new selector.
* @param {OnMapObjectsInSelector} callback The callback function.
* @returns {THING.Selector}
* @example
* // Returns all first child object of each objects
* let objects = selector.map((object) => {
* return object.children[0];
* });
* @public
*/
map(callback) {
if (!callback) {
return null;
}
let objects = this._objects.map((object, i) => {
return callback(object, i, this._objects);
}).filter(object => {
return !!object;
});
return this.onCreateInstance({ objects });
}
/**
* The function to call when to reduce in selector.
* @callback OnReduceObjectsInSelector
* @param {Number} accumulator The accumulate value.
* @param {Number} currentValue The current value.
* @param {Number} currentIndex The current index.
* @param {Array<THING.BaseObject>} objects The objects.
* @returns {Number}
* @public
*/
/**
* Executes a user-supplied "reducer" callback function on each element of the array, in order, passing in the return value from the calculation on the preceding element.
* The final result of running the reducer across all elements of the array is a single value.
* @param {OnReduceObjectsInSelector} callback The callback function.
* @param {Number} initialValue The initial number.
* @returns {Number}
* @example
* // Get avg height of entities
* let entities = app.query('.Entity');
* let height = entities.reduce((value, entity) => {
* return value + entity.position[1];
* }, 0) / entities.length;
* @public
*/
reduce(callback, initialValue = 0) {
if (!callback) {
return 0;
}
return this._objects.reduce((accumulator, object, currentIndex) => {
return callback(accumulator, object, currentIndex, this._objects);
}, initialValue);
}
/**
* Return elements in range.
* @param {Number} start The start index.
* @param {Number} end? The end index.
* @returns {THING.Selector}
* @example
* // Returns [1, 4] range of objects
* let objects1 = selector.slice(1, 4);
*
* // Returns [1, ...] range of objects
* let objects2 = selector.slice(1);
* @public
*/
slice(start, end) {
return this.onCreateInstance({ objects: this._objects.slice(start, end) });
}
/**
* The function to call when to get objects by filter.
* @callback OnFilterObjectsInSelector
* @param {THING.BaseObject} object The object.
* @param {Number} index The index of objects.
* @param {Array<THING.BaseObject>} objects The objects.
* @returns {Boolean}
* @public
*/
/**
* Filter.
* @param {OnFilterObjectsInSelector} callback The callback function.
* @returns {THING.Selector}
* @example
* // Filter objects with name
* let objects = selector.filter((object) => {
* return !!object.name;
* });
* @public
*/
filter(callback) {
if (!callback) {
return null;
}
let objects = this._objects.filter((object, i) => {
return callback(object, i, this._objects);
});
return this.onCreateInstance({ objects });
}
/**
* The function to call when to sort objects.
* @callback OnSortObjectsInSelector
* @param {THING.BaseObject} obj1 The first object.
* @param {THING.BaseObject} obj2 The second object.
* @returns {Number}
* < 0: obj1 < obj2
* == 0: obj1 == obj2
* > 0: obj1 > obj2
* @public
*/
/**
* Sort objects from low to high by the result from callback function.
* @param {OnSortObjectsInSelector} callback The callback function to sort.
* @returns {THING.Selector}
* @example
* // Sort objects by name
* selector.sort((obj1, obj2) => {
* return obj1.name.localeCompare(obj2.name);
* })
* @public
*/
sort(callback) {
let objects = this._objects;
objects.sort(callback);
return this;
}
/**
* Convert to array (Return a new array)
* @returns {Array<THING.BaseObject>}
* @example
* // Get/Convert objects in array mode
* let objects = selector.objects;
* console.log(objects);
* @public
*/
toArray() {
return this._objects.slice(0);
}
/**
* Check whether has/includes element.
* @param {Object} object The object.
* @returns {Boolean}
* @example
* // Check whether includes specified object
* let exists = selector.includes(object);
* @public
*/
includes(object) {
return this._objects.includes(object);
}
/**
* Get the index of element in objects.
* @param {Object} object The object.
* @returns {Number} -1 indicates that specified object does not exist.
* @example
* // Get the index of object in selector
* let index = selector.indexOf(object);
* @public
*/
indexOf(object) {
return this._objects.indexOf(object);
}
/**
* Remove objects.
* @param {Number} index The start index.
* @param {Number} number The number of objects what to remove.
* @returns {THING.Selector} The removed objects.
* @example
* // Remove [1, 4] range of objects
* let removeObjects = selector.splice(1, 4);
* @public
*/
splice(index, number) {
return this.onCreateInstance({ objects: this._objects.splice(index, number) });
}
/**
* Insert object by index.
* @param {Number} index The index to insert.
* @param {THING.BaseObject} object The object to insert.
* @returns {THING.Selector}
* @example
* // Insert objects at the front of selector
* selector.insert(0, [obj1, obj2]);
* @public
*/
insert(index, object) {
this._objects.splice(index, 0, object);
return this;
}
/**
* Remove object at index.
* @param {Number} index The index of object to remove.
* @returns {THING.Selector}
* @example
* // Remove the first object in selector.
* selector.remoteAt(0);
* @public
*/
removeAt(index) {
this._objects.splice(index, 1);
return this;
}
/**
* Swap objects by index.
* @param {Number} index0 The first index of objects.
* @param {Number} index1 The second index of objects.
* @returns {THING.Selector}
* @example
* // Swap (index: 0) and (index:3) objects in selector
* selector.swap(0, 3);
* @public
*/
swap(index0, index1) {
this._objects._swap(index0, index1);
return this;
}
/**
* Combine array.
* @param {THING.Selector|Array<THING.BaseObject>} selector The objects array or selector.
* @returns {THING.Selector}
* @example
* // Concat other objects/selector and create new selector
* let newSelector = selector.concat([obj1, obj2]);
* @public
*/
concat(selector) {
// Concat objects and itself to new selector
if (selector) {
let candidates = this._objects.concat(selector.isSelector ? selector.objects : selector);
return this.onCreateInstance({ objects: Array.from(new Set(candidates)) });
}
// Return itself as new one
else {
return this.onCreateInstance({ objects: this._objects.slice() });
}
}
/**
* Reverse the objects.
* @returns {THING.Selector}
* @example
* // Reverse objects in selector
* selector.reverse();
* @public
*/
reverse() {
this._objects.reverse();
return this;
}
/**
* Check whether has object or not.
* @param {Object} object The object.
* @returns {Boolean}
* @example
* // Check whether has specified object
* let exists = selector.has(object);
* @public
*/
has(object) {
return this._objects.indexOf(object) !== -1;
}
/**
* Check whether it's the same selector.
* @param {Array<Object>|THING.Selector} objects The objects array or selector.
* @returns {Boolean}
* @example
* let isSame = selector.equals([obj1, obj2]);
* @public
*/
equals(objects) {
if (this.length != objects.length) {
return false;
}
for (let i = 0; i < this.length; i++) {
if (this[i] != objects[i]) {
return false;
}
}
return true;
}
/**
* Get/Set the length of objects.
* @type {Number}
* @example
* let length = selector.length;
* @public
*/
get length() {
return this._objects.length;
}
set length(value) {
this._objects.length = value;
}
/**
* Get the objects.
* @type {Array<THING.BaseObject>}
* @example
* let objects = selector.objects;
* @private
*/
get objects() {
return this._objects;
}
// #endregion
// #region Objects State Operations
/**
* Get the loaded objects.
* @returns {Array<THING.BaseObject>}
* @private
*/
getLoadedObjects() {
return this._objects.filter(object => {
return object.loaded;
});
}
/**
* Get the unloaded objects.
* @returns {Array<THING.BaseObject>}
* @private
*/
getUnloadedObjects() {
return this._objects.filter(object => {
return !object.loaded;
});
}
/**
* Get the visible objects.
* @returns {Array<THING.BaseObject>}
* @private
*/
getVisibleObjects() {
return this._objects.filter(object => {
return object.visible;
});
}
/**
* Get the invisible objects.
* @returns {Array<THING.BaseObject>}
* @private
*/
getInvisibleObjects() {
return this._objects.filter(object => {
return !object.visible;
});
}
/**
* Make objects in instanced drawing mode.
* @param {Boolean} value True indicates enable instanced drawing mode.
* @param {Object} options The options.
* @example
* // Enable objects instanced drawing
* selector.makeInstancedDrawing(true);
*
* // Disable objects instanced drawing
* selector.makeInstancedDrawing(false);
* @public
*/
makeInstancedDrawing(value, options) {
this._objects.forEach(object => {
if (!object.isObject3D) {
return;
}
object.makeInstancedDrawing(value, options);
});
}
/**
* Get the instanced drawing objects.
* @returns {Array<THING.BaseObject>}
* @example
* // Get the objects what has enabled instanced drawing
* let instancedDrawingObjects = selector.getInstancedDrawingObjects();
* @public
*/
getInstancedDrawingObjects() {
return this._objects.filter(object => {
return object.isInstancedDrawing;
});
}
/**
* Set visible state.
* @param {Boolean} value True indicates show it, otherwise hide it.
* @param {Boolean} [recursive=false] True indicates process it with all children.
* @example
* // Set visible attribute of all objects in selector to true
* selector.setVisible(true);
*
* // Set visible attribute of all objects and its children in selector to true
* selector.setVisible(true, true);
* @public
*/
setVisible(value, recursive = false) {
this._objects.forEach(object => {
object.setVisible(value, recursive);
});
}
/**
* Set all objects and its children visible state.
* @type {Boolean}
* @example
* // Set visible attribute of all objects and its children in selector to true
* selector.visible = true;
* @public
*/
set visible(value) {
this._objects.forEach(object => {
object.visible = value;
});
}
/**
* Set all objects and its children pickable state.
* @type {Boolean}
* @example
* // Set pickable attribute of all objects and its children in selector to true
* selector.pickable = true;
* @public
*/
set pickable(value) {
this._objects.forEach(object => {
object.pickable = value;
});
}
// #endregion
// #region Objects Resource Operations
/**
* Wait for all objects load completed.
* @returns {Promise<any>}
* @example
* // Wait all objects load finished then print their name
* selector.waitForComplete().then((objects) => {
* objects.forEach(object => {
* console.log(object.name);
* });
* });
* @public
*/
waitForComplete() {
return this._objects.waitForComplete();
}
/**
* Wait for all objects sync action finished(would process objects one by one).
* @param {Function} callback The callback function.
* @returns {Promise<any>}
* @example
* // Load objects one by one
* selector.waitForEachSync(async (object) => {
* return object.loadResource();
* });
* @private
*/
waitForEachAsync(callback) {
return this._objects.waitForEachAsync(callback);
}
/**
* Wait for all objects sync action finished(would process objects one by one).
* @param {Function} callback The callback function.
* @returns {Promise<any>}
* @deprecated 2.7
* @private
*/
waitForEachSync(callback) {
return this.waitForEachAsync(callback);
}
/**
* Destroy all.
* @example
* selector.destroy();
* @public
*/
destroy() {
let objects = this._objects.slice(0);
objects.forEach(object => {
object.destroy();
});
}
/**
* Load resource.
* @param {Object} options The load options.
* @example
* selector.loadResource();
* @public
*/
loadResource(options) {
this._objects.forEach(object => {
object.loadResource(false, options);
});
}
/**
* Unload resource.
* @example
* selector.unloadResource();
* @public
*/
unloadResource() {
this._objects.forEach(object => {
object.unloadResource();
});
}
// #endregion
// #region Objects Event Operations
/**
* Register all objects event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {Function} callback The callback function.
* @param {String} tag The event tag.
* @param {Number} priority The priority value(default is 0, higher value will be processed first)
* @param {ObjectEventOptions} options The options.
* @example
* // Register all entities with 'click' event listener
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=>{
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markOn = 0;
* selector.on('testOn', function(ev) {
* markOn = 1;
* });
* selector.trigger('testOn');
* // @expect(markOn == 1 && mark == 1);
* @public
*/
on() {
this.applyObjects('on', arguments);
}
/**
* Register all objects event what just trigger once time.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {Function} callback The callback function.
* @param {String} tag The event tag.
* @param {Number} priority The priority value(default is 0, higher value will be processed first)
* @param {ObjectEventOptions} options The options.
* @example
* // Register all entities with 'click' event listener in one time
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=>{
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markOnce = 0;
* selector.once('testOnce', function(ev) {
* markOnce = 1;
* });
* selector.trigger('testOnce');
* // @expect(markOnce == 1 && mark == 1);
* markOnce = 0;
* mark = 0;
* selector.trigger('testOnce');
* // @expect(markOnce == 0 && mark == 0);
* @public
*/
once() {
this.applyObjects('once', arguments);
}
/**
* Unregister all objects event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @example
* // Unregister all entities with 'click' event listener by tag name
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=>{
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markOff = 0;
* selector.on('testOff', function(ev) {
* markOff = 1;
* });
* selector.trigger('testOff');
* // @expect(markOff == 1 && mark == 1);
* markOff = 0;
* mark = 0;
* selector.off('testOff');
* selector.trigger('testOff');
* // @expect(markOff == 0 && mak == 0);
* @public
*/
off() {
this.applyObjects('off', arguments);
}
/**
* Pause all objects event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @example
* // Pause all entities with 'click' event listener by tag name
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=>{
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markPause = 0;
* selector.on('testPause', function(ev) {
* markPause = 1;
* });
* selector.trigger('testPause');
* // @expect(markPause == 1 && mark == 1);
* markPause = 0;
* mark = 0;
* selector.PauseEvent('testPause');
* selector.trigger('testPause');
* // @expect(markPause == 0 && mark == 0);
* @public
*/
pauseEvent() {
this.applyObjects('pauseEvent', arguments);
}
/**
* Resume all objects event.
* @param {String} type The event type.
* @param {String} condition The condition to select objects.
* @param {String} tag The event tag.
* @example
* // Resume all entities with 'click' event listener by tag name
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=>{
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* let markResume = 0;
* selector.on('testPause', function(ev) {
* markResume = 1;
* });
* selector.trigger('testPause');
* // @expect(markResume == 1 && mark == 1);
* markResume = 0;
* mark = 0;
* selector.PauseEvent('testPause');
* selector.trigger('testPause');
* // @expect(markResume == 0 && mark == 0);
* selector.resumeEvent('testPause');
* selector.trigger('testPause');
* // @expect(markResume == 1 && mark == 1);
* @public
*/
resumeEvent() {
this.applyObjects('resumeEvent', arguments);
}
/**
* Trigger all objects event.
* @param {String} type The event type.
* @param {Object} ev The event info.
* @param {Object} options The options.
* @param {String} options.tag The tag name.
* @example
* // Trigger all entities with 'click' event listener by tag name
* let mark = 0;
* let entity = new THING.Entity();
* entity.on('test', ()=>{
* mark = 1;
* })
* let selector = new THING.Selector([entity]);
* selector.trigger('test');
* // @expect(mark == 1)
* @public
*/
trigger(type, ev) {
this._objects.forEach(object => {
object.trigger(type, Object.assign({}, ev));
});
}
// #endregion
// #region Objects Lerp Operations
stopMoving() {
this.applyObjects('stopMoving', arguments);
}
moveTo() {
this.applyObjects('moveTo', arguments);
}
movePath() {
this.applyObjects('movePath', arguments);
}
stopRotating() {
this.applyObjects('stopRotating', arguments);
}
rotateTo() {
this.applyObjects('rotateTo', arguments);
}
stopScaling() {
this.applyObjects('stopScaling', arguments);
}
scaleTo() {
this.applyObjects('scaleTo', arguments);
}
stopFading() {
this.applyObjects('stopFading', arguments);
}
fadeIn() {
this.applyObjects('fadeIn', arguments);
}
fadeOut() {
this.applyObjects('fadeOut', arguments);
}
stopAnimation() {
this.applyObjects('stopAnimation', arguments);
}
stopAllAnimations() {
this.applyObjects('stopAllAnimations', arguments);
}
playAnimation() {
this.applyObjects('playAnimation', arguments);
}
// #endregion
// #region Objects Component Operations
addComponent() {
this.applyObjects('addComponent', arguments);
}
removeComponent() {
this.applyObjects('removeComponent', arguments);
}
// #endregion
// #region Objects Wrapper Operations
get style() {
return _styleSelector.init(this._objects);
}
get inherit() {
return _inheritSelector.init(this._objects);
}
// #endregion
// #region Objects Bounding Operations
/**
* Get the axis-aligned bounding box(AABB).
* @returns {THING.Box3}
* @example
* let selector = new THING.Selector();
* let box = selector.getBoundingBox();
* let ret = box.size[0] == 0;
* // @expect(ret == true);
* @public
*/
getBoundingBox() {
let aabb = new Box3();
this._objects.forEach(object => {
let boundingBox = object.boundingBox;
aabb.expandByPoint(boundingBox.min);
aabb.expandByPoint(boundingBox.max);
});
return aabb;
}
// #endregion
/**
* Check class type.
* @type {Boolean}
* @example
* let selector = new THING.Selector();
* // @expect(selector.isSelector == true);
*/
get isSelector() {
return true;
}
}
// #region Static Interface
let _selector = new Selector();
Selector.init = function (param = {}) {
let workerUrl = param['workerUrl'];
if (workerUrl) {
Selector.asyncSelector = new AsyncSelector({ workerUrl });
}
}
Selector.buildExpression = function (condition, mode) {
return new ObjectExpression(condition, mode);
}
Selector.testByExpression = function (expression, object) {
return expression.evaluate(object);
}
Selector.testByRegExpName = function (condition, object) {
return condition.test(object.name);
}
Selector.testByFunction = function (condition, object) {
return condition(object);
}
Selector.test = function (condition, object) {
return _selector.test(condition, object);
}
Selector.select = function (condition, objects) {
return _selector.select(condition, objects);
}
Selector.query = function (condition, objects) {
return _selector.query(condition, objects);
}
Selector.queryAsync = function (condition, root, recursive) {
return _selector.queryAsync(condition, root, recursive);
}
Selector.updateObjectAttribute = function (object, key, value) {
if (Selector.asyncSelector) {
Selector.asyncSelector.updateObjectAttribute(object, key, value);
}
}
Selector.addObject = function (object) {
if (Selector.asyncSelector) {
Selector.asyncSelector.addObject(object);
}
}
Selector.removeObject = function (object) {
if (Selector.asyncSelector) {
Selector.asyncSelector.removeObject(object);
}
}
Selector.updateObjectAttribute = function (object, key, value) {
if (Selector.asyncSelector) {
Selector.asyncSelector.updateObjectAttribute(object, key, value);
}
}
// #endregion
export { Selector };