awv3
Version:
⚡ AWV3 embedded CAD
665 lines (583 loc) • 22.5 kB
JavaScript
import * as THREE from 'three';
import Defaults from '../core/defaults';
import Events from '../core/events';
import Tween from '../animation/tween';
import Raycaster from './raycaster';
export default class Object3 extends THREE.Object3D {
constructor(objects = undefined, options = undefined) {
super();
if (!!options) this.setValues(options);
if (!!objects) this.add(objects);
}
}
THREE.Object3D.Events = {
Lifecycle: {
Loaded: 'Loaded',
Added: 'Added',
ViewFound: 'ViewFound',
ChildAdded: 'ChildAdded',
Removed: 'Removed',
ChildRemoved: 'ChildRemoved',
Destroyed: 'Destroyed',
Rendered: 'Rendered',
},
Interaction: {
Clicked: 'Clicked',
Picked: 'Picked',
Dropped: 'Dropped',
Dragged: 'Dragged',
Hovered: 'Hovered',
Unhovered: 'Unhovered',
Missed: 'Missed',
},
};
THREE.Object3D.prototype.canvas = undefined;
THREE.Object3D.prototype.view = undefined;
THREE.Object3D.prototype.bounds = undefined;
THREE.Object3D.prototype.materials = undefined;
THREE.Object3D.prototype.updateParentMaterials = true;
THREE.Object3D.prototype.keep = false;
THREE.Object3D.prototype.measurable = true;
THREE.Object3D.prototype.tweens = true;
THREE.Object3D.prototype.interactive = true;
THREE.Object3D.prototype.interaction = undefined;
THREE.Object3D.prototype.interactionPriority = 0;
// Extend Object3D with event emitter
Events.mixin(THREE.Object3D.prototype);
const copyObject3D = THREE.Object3D.prototype.copy;
THREE.Object3D.prototype.copy = function(source, ...args) {
const copy = copyObject3D.bind(this)(source, ...args);
copy.updateParentMaterials = source.updateParentMaterials;
copy.keep = source.keep;
copy.measurable = source.measurable;
copy.tweens = source.tweens;
copy.interactive = source.interactive;
copy.interactionPriority = source.interactionPriority;
return copy;
};
THREE.Object3D.prototype.createInteraction = function(options = {}) {
if (this.interaction) return this;
this.interaction = {
first: true,
faces: false,
priority: 0,
enabled: true,
recursive: false,
approach: Raycaster.Approach.Default,
types: ['Mesh'],
...options,
};
let scope = this;
this.viewFound().then(view => {
if (view.interaction.targets.indexOf(scope) == -1) {
view.interaction.targets.push(scope);
let count = 0;
view.scene.traverse(item => (item.depthIndex = count++));
view.interaction.targets.sort((a, b) => a.depthIndex - b.depthIndex);
}
});
// _active controls if the interaction layer considers the object, depending if interaction-related events are registered
this.inspect(
context =>
(this.interaction._active = !!Object.keys(this._callbacks).find(
item => item in THREE.Object3D.Events.Interaction,
)),
);
return this;
};
THREE.Object3D.prototype.removeInteraction = function() {
if (this.view && this.interaction) {
this.interaction = undefined;
this.view.interaction.removeTarget(this);
this.removeInspectors();
this.removeListener([
Object3.Events.Interaction.Clicked,
Object3.Events.Interaction.Picked,
Object3.Events.Interaction.Dropped,
Object3.Events.Interaction.Dragged,
Object3.Events.Interaction.Hovered,
Object3.Events.Interaction.Unhovered,
Object3.Events.Interaction.Misses,
Object3.Events.Lifecycle.Rendered,
]);
}
};
THREE.Object3D.prototype.viewFound = function() {
let scope = this;
return !!this.view
? Promise.resolve(this.view)
: new Promise(resolve => scope.once(THREE.Object3D.Events.Lifecycle.ViewFound, resolve));
};
THREE.Object3D.prototype._destroyRecursive = function(options) {
this.canvas = undefined;
this.view = undefined;
this.scene = undefined;
this.bounds = undefined;
this.userData = {};
if (!!this.geometry) {
this.geometry.dispose();
}
if (!!this.material) {
let isMultiMaterial = Array.isArray(this.material);
if (isMultiMaterial) {
this.material.forEach(material => {
material.dispose();
material.needsUpdate = true;
});
this.material = undefined;
} else {
this.material.dispose && this.material.dispose();
this.material.needsUpdate = true;
this.material = undefined;
}
}
if (!!this.materials) {
//this.materials.all.forEach(material => material.dispose());
this.materials.all = [];
this.materials.meshes = [];
this.materials.lines = [];
}
// Remove children
if (!options.async) {
for (let i = this.children.length - 1; i >= 0; i--)
this.children[i].destroy(options);
} else {
return Promise.all(this.children.map(child => child.destroyAsync(options)));
}
};
THREE.Object3D.prototype.destroyAsync = async function(options = {}) {
let view = this.view;
options = { keep: true, data: true, interaction: true, listeners: true, ...options };
if (options.keep && this.keep) return;
options.data && (await this._destroyRecursive({ ...options, async: true }));
options.interaction && this.removeInteraction();
await this.emit(Object3.Events.Lifecycle.Destroyed);
this.parent && (await this.parent.removeAsync(this));
options.listeners && this.removeListeners();
this.removeObjectTweens();
view && view.invalidate();
};
THREE.Object3D.prototype.destroy = function(options = {}) {
let view = this.view;
options = { keep: true, data: true, interaction: true, listeners: true, ...options };
if (options.keep && this.keep) return;
this.emit(Object3.Events.Lifecycle.Destroyed);
this.parent && this.parent.remove(this);
options.interaction && this.removeInteraction();
options.listeners && this.removeListeners();
this.removeObjectTweens();
options.data && this._destroyRecursive(options);
view && view.invalidate();
};
function updateReferences(parent, object) {
if ((object.parent === parent && parent instanceof THREE.Scene) || parent.view) {
object.traverse(child => {
child.scene = parent instanceof THREE.Scene ? parent : parent.scene;
child.view = parent.view;
child.canvas = parent.canvas;
child.emit(THREE.Object3D.Events.Lifecycle.ViewFound, child.view);
});
} else {
object.traverse(child => {
child.scene = undefined;
child.view = undefined;
child.canvas = undefined;
});
}
return object;
}
THREE.Object3D.prototype.addAsync = async function(object) {
object = arguments.length > 1 ? Array.from(arguments) : object;
if (Array.isArray(object)) {
return Promise.all(object.map(item => this.addAsync(item)));
}
if (object !== this && object instanceof THREE.Object3D) {
if (!!object.parent) await object.parent.removeAsync(object);
object.parent = this;
this.children.push(updateReferences(this, object));
await this.emit(Object3.Events.Lifecycle.ChildAdded, { child: object });
await object.emit(Object3.Events.Lifecycle.Added, { parent: this });
!!this.view && this.view.invalidate();
}
};
THREE.Object3D.prototype.add = function(object) {
object = arguments.length > 1 ? Array.from(arguments) : object;
if (Array.isArray(object)) {
return Promise.all(object.map(item => this.add(item)));
}
if (object !== this && object instanceof THREE.Object3D) {
if (!!object.parent) object.parent.remove(object);
object.parent = this;
this.children.push(updateReferences(this, object));
this.emit(Object3.Events.Lifecycle.ChildAdded, { child: object });
object.emit(Object3.Events.Lifecycle.Added, { parent: this });
!!this.view && this.view.invalidate();
}
};
THREE.Object3D.prototype.removeAsync = async function(object) {
object = arguments.length > 1 ? Array.from(arguments) : object;
if (Array.isArray(object)) {
return Promise.all(object.map(item => this.removeAsync(item)));
}
if (object instanceof THREE.Object3D) {
var index = this.children.indexOf(object);
if (index !== -1) {
await this.emit(Object3.Events.Lifecycle.ChildRemoved, { child: object });
await object.emit(Object3.Events.Lifecycle.Removed, { parent: this });
this.children.splice(this.children.indexOf(object), 1);
object.parent = null;
updateReferences(this, object);
!!this.view && this.view.invalidate();
}
}
};
THREE.Object3D.prototype.remove = function(object) {
object = arguments.length > 1 ? Array.from(arguments) : object;
if (Array.isArray(object)) {
return Promise.all(object.map(item => this.remove(item)));
}
if (object instanceof THREE.Object3D) {
var index = this.children.indexOf(object);
if (index !== -1) {
this.emit(Object3.Events.Lifecycle.ChildRemoved, { child: object });
object.emit(Object3.Events.Lifecycle.Removed, { parent: this });
this.children.splice(index, 1);
object.parent = null;
updateReferences(this, object);
!!this.view && this.view.invalidate();
}
}
};
THREE.Object3D.prototype.removeObjectTweens = function() {
Tween.removeObjectTweens(this);
return this;
};
function traverse(obj, cb) {
if (!!obj.material) cb(obj);
for (let child of obj.children)
child.updateParentMaterials && traverse(child, cb);
}
THREE.Object3D.prototype.updateMaterials = function() {
var colorMap = {};
this.materials = this.materials || {
updateParent: true,
all: [],
meshes: [],
lines: [],
};
this.materials.all = [];
this.materials.meshes = [];
this.materials.lines = [];
traverse(this, child => (colorMap[child.material.uuid] = child.material));
Object.keys(colorMap).forEach(key => {
var material = colorMap[key];
let isMultiMaterial = Array.isArray(material);
if (isMultiMaterial) {
material.forEach(material => {
this.materials.all.push(material);
if (material.type.indexOf('Mesh') > -1) this.materials.meshes.push(material);
else if (material.type.indexOf('Line') > -1) this.materials.lines.push(material);
});
} else {
this.materials.all.push(material);
if (material.type.indexOf('Mesh') > -1) this.materials.meshes.push(material);
else if (material.type.indexOf('Line') > -1) this.materials.lines.push(material);
}
});
return this;
};
THREE.Object3D.prototype.compress = function() {
if (!this.materials) return;
var keys = Object.keys(this.materials.all), colorMap = {};
// Compress materials
for (var i = 0, l = this.materials.all.length; i < l; i++) {
var material = this.materials.all[i],
index = `#${material.color.getHexString()}_${material.opacity.toFixed(2)}_${material.type}`;
let entry = colorMap[index];
if (!entry) colorMap[index] = material;
else {
// This material is already known
this.traverse(function(child) {
if (child.material) {
let isMultiMaterial = Array.isArray(material);
if (isMultiMaterial) {
for (let i = 0, item, len = child.material.length; i < len; i++) {
item = child.material[i];
if (item === material) {
item.dispose();
child.material[i] = entry;
}
}
} else if (child.material === material) {
child.material.dispose();
child.material = entry;
}
}
});
}
}
this.updateMaterials();
return this;
};
THREE.Object3D.prototype.animate = function(properties) {
return this.tweens ? new Tween(this, properties) : new Tween({}, {});
};
//some material properties cannot/shouldn't be animated
//change them immediately and remove from animation
function preprocessMaterialProps(obj, props) {
for (let key of ['polygonOffsetFactor', 'polygonOffsetUnits']) {
if (props[key] === undefined) continue;
obj[key] = props[key];
delete props[key];
}
}
THREE.Material.prototype.tweens = true;
THREE.Material.prototype.animate = function(properties) {
preprocessMaterialProps(this, properties);
return this.tweens ? new Tween(this, properties) : new Tween({}, {});
};
// This creates a property map for the animate function targeted to the material property
// It uses material meta data to restore defaults
THREE.Object3D.prototype.mapMaterial = function(props = {}) {
if (this.material) {
return {
material: Array.isArray(this.material)
? this.material.map(material => ({
...(material.meta || {}).material,
...(typeof props === 'function' ? props(material) : props),
}))
: { ...(material.meta || {}).material, ...(typeof props === 'function' ? props(material) : props) },
};
} else {
return {};
}
};
THREE.Object3D.prototype.animateMaterials = function(properties) {
return this.tweens ? new Tween(this, { materials: properties }) : new Tween({}, {});
};
THREE.Object3D.prototype.animateAllMaterials = function(properties) {
return this.tweens ? new Tween(this, { materials: { all: [properties] } }) : new Tween({}, {});
};
THREE.Object3D.prototype.animateMeshes = function(properties) {
return this.tweens ? new Tween(this, { materials: { meshes: [properties] } }) : new Tween({}, {});
};
THREE.Object3D.prototype.animateLines = function(properties) {
return this.tweens ? new Tween(this, { materials: { lines: [properties] } }) : new Tween({}, {});
};
THREE.Object3D.prototype.fadeOut = function(length) {
this.animate({
materials: {
meshes: [{ opacity: 0.0 }],
lines: [{ opacity: 0.0 }],
},
}).start(length || 0);
return this;
};
THREE.Object3D.prototype.fadeIn = function(length, value) {
this.animate({
materials: {
meshes: [{ opacity: value || 1.0 }],
lines: [{ opacity: value || 1.0 }],
},
}).start(length || 0);
return this;
};
THREE.Object3D.prototype.setValues = function(properties) {
return new Tween(this, properties).start(0);
};
THREE.Object3D.prototype.root = function() {
var result, current = this;
while (current) {
if (!current.parent || current.parent instanceof THREE.Scene) return current;
current = current.parent;
}
};
THREE.Object3D.prototype.isChildOf = function(parent) {
var current = this;
while (current) {
if (current == parent) return true;
current = current.parent;
}
return false;
};
THREE.Object3D.prototype.isVisible = function() {
if (!this.visible || (this instanceof THREE.Mesh && this.material.opacity == 0)) return false;
if (this.parent == null || this.parent instanceof THREE.Scene) return this.visible;
else if (this.visible) return this.parent.isVisible();
return false;
};
THREE.Object3D.prototype.getObjectByMatch = function(name) {
for (var i = 0, l = this.children.length; i < l; i++) {
var child = this.children[i];
if (child.name.indexOf(name) > -1) return child;
}
return undefined;
};
THREE.Object3D.prototype.getObjectByUserId = function(id) {
if (!!this.userData && this.userData.id === id) return this;
for (var i = 0, l = this.children.length; i < l; i++) {
var child = this.children[i];
var object = child.getObjectByUserId(id);
if (object !== undefined) {
return object;
}
}
return undefined;
};
THREE.Object3D.prototype.lastChild = function() {
return this.children.length ? this.children[this.children.length - 1] : undefined;
};
THREE.Object3D.prototype.setPosition = function(vector) {
vector = arguments.length > 1 ? new THREE.Vector3(...arguments) : vector;
this.position.copy(vector);
return this;
};
THREE.Object3D.prototype.setRotation = function(vector) {
vector = arguments.length > 1 ? new THREE.Euler(...arguments) : vector;
this.rotation.copy(vector);
return this;
};
THREE.Object3D.prototype.setScale = function(vector) {
vector = arguments.length > 1 ? new THREE.Vector3(...arguments) : vector;
this.scale.copy(vector);
return this;
};
THREE.Object3D.prototype.setRenderOrder = function(index, lines) {
this.traverse(item => {
if (item instanceof THREE.Mesh || (!!lines && item instanceof THREE.Line)) {
item.renderOrder = index;
}
});
return this;
};
THREE.Object3D.prototype.updateBounds = function(box = undefined) {
this.bounds = this.bounds || {
box: new THREE.Box3(),
sphere: new THREE.Sphere(),
};
this.bounds.box = !!box ? this.bounds.box.union(box) : new THREE.Box3().setFromObject(this);
this.bounds.sphere = this.bounds.box.getBoundingSphere();
return this;
};
THREE.Box3.prototype.expandByObject = (function() {
let v1 = new THREE.Vector3();
return function expandByObject(object) {
let scope = this;
object.updateMatrixWorld(true);
object.traverseConditional(function(node) {
let i, l;
let geometry = node.geometry;
let keepGoing = node.measurable && node.visible;
if (geometry !== undefined && keepGoing) {
if (geometry.isGeometry) {
var vertices = geometry.vertices;
for ((i = 0), (l = vertices.length); i < l; i++) {
v1.copy(vertices[i]);
v1.applyMatrix4(node.matrixWorld);
scope.expandByPoint(v1);
}
} else if (geometry.isBufferGeometry) {
var attribute = geometry.attributes.position;
if (attribute !== undefined) {
for ((i = 0), (l = attribute.count); i < l; i++) {
v1.fromBufferAttribute(attribute, i).applyMatrix4(node.matrixWorld);
scope.expandByPoint(v1);
}
}
}
}
return keepGoing;
});
return this;
};
})();
THREE.Object3D.prototype.getCenter = function(force) {
if (force || !this.bounds) this.updateBounds();
return this.bounds.sphere.center.clone();
};
THREE.Object3D.prototype.getRadius = function(force) {
if (force || !this.bounds) this.updateBounds();
return this.bounds.sphere.radius;
};
THREE.Object3D.prototype.centerGeometry = function(center = this.getCenter(true), setPosition = true) {
setPosition && this.position.copy(center);
if (!!this.geometry && (this.geometry instanceof THREE.Geometry || this.geometry instanceof THREE.BufferGeometry)) {
this.geometry.applyMatrix(new THREE.Matrix4().makeTranslation(-center.x, -center.y, -center.z));
this.geometry.computeBoundingBox();
this.geometry.computeBoundingSphere();
}
for (let child of this.children)
child.centerGeometry(center, false);
return this;
};
THREE.Object3D.prototype.centerChildGeometry = function() {
for (let child of this.children)
child.centerGeometry();
return this;
};
THREE.Object3D.prototype.addEventListener = function(type, listener) {
return this.on(type, listener);
};
THREE.Object3D.prototype.hasEventListener = function(type, listener) {
return this.hasListener(type, listener);
};
THREE.Object3D.prototype.removeEventListener = function(type, listener) {
return this.removeListener(type, listener);
};
THREE.Object3D.prototype.dispatchEvent = function(event, ...args) {
return this.emit(event, ...args);
};
THREE.Object3D.prototype.setRenderOrder = function(pairs = Defaults.renderOrder) {
this.traverse(item => {
let value = pairs[item.type];
if (value !== undefined) {
item.renderOrder = value;
}
});
return this;
};
THREE.Object3D.RenderOrder = {
Default: { Mesh: 0, LineSegments: 100 },
LinesFirst: { Mesh: 0, LineSegments: 100 },
MeshesFirst: { Mesh: 100, LineSegments: 0 },
};
THREE.Object3D.prototype.find = function(condition) {
if (condition(this)) return this;
for (let child of this.children) {
let test = child.find(condition);
if (test) return test;
}
};
THREE.Object3D.prototype.traverseConditional = function(callback) {
if (callback(this)) {
let children = this.children;
for (let i = 0, l = children.length; i < l; i++) {
children[i].traverseConditional(callback);
}
}
};
THREE.Object3D.prototype.findMaterial = function(condition) {
let material = undefined;
let mesh = this.find(item => {
if (item.material) {
let materials = Array.isArray(item.material) ? item.material : [item.material];
for (let materialItem of materials) {
if (condition(materialItem, item)) {
material = materialItem;
return item;
}
}
}
});
return material ? { mesh, material } : undefined;
};
THREE.Object3D.prototype.traverseMaterials = function(callback) {
this.traverse(item => {
if (item.material) {
let isMultiMaterial = Array.isArray(item.material);
if (isMultiMaterial) {
item.material.forEach(multi => callback(multi, item));
} else callback(item.material, item);
}
});
};