UNPKG

awv3

Version:
665 lines (583 loc) 22.5 kB
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); } }); };