awv3
Version:
⚡ AWV3 embedded CAD
174 lines (151 loc) • 5.73 kB
JavaScript
import * as THREE from 'three';
import Object3 from '../three/object3';
import { exponential } from '../animation/easing';
export class Pool extends THREE.Group {
constructor({ session, name = 'pool' }) {
super();
this.name = name;
this.session = session;
this.temporary = new THREE.Group();
this.temporary.name = `${name}.temporary`;
this.temporary.updateParentMaterials = false;
super.add(this.temporary);
this.preset = { duration: 300, renderOrder: Object3.RenderOrder.Default };
this.defaults = this.preset;
}
update() {
// ...
}
fadeToDefaults(object) {
return object.animate(
object.mapMaterial(material => {
if (material.type.startsWith('Mesh') && this.defaults.meshOpacity !== undefined)
return { opacity: this.defaults.meshOpacity };
else if (material.type.startsWith('Line') && this.defaults.lineOpacity !== undefined)
return { opacity: this.defaults.lineOpacity };
else return {};
}),
);
}
fadeIn(options) {
if (!this.session) return;
const { renderOrder, duration } = (this.defaults = { ...this.preset, ...options });
this.traverse(object => object.material && this.fadeToDefaults(object).start(duration));
this.setRenderOrder(renderOrder);
}
fadeOut(options) {
this.fadeIn({ meshOpacity: 0.3, lineOpacity: 0.3, renderOrder: Object3.RenderOrder.MeshesFirst, ...options });
}
}
export class ObjectPrototype extends THREE.Group {
constructor({ session }) {
super();
this.session = session;
if (this.session) {
this.pool = session.pool;
}
}
reset(child) {
if (this.pool) {
// This will fade the model from zero opacity to either its material-given default or to
// various presets defined by this pool or its session
child.traverse(
object =>
object.material &&
this.pool
.fadeToDefaults(object)
.from(object.mapMaterial({ opacity: 0 }))
.easing(exponential.in)
.start(this.pool.defaults.duration),
);
child.setRenderOrder(this.pool.defaults.renderOrder);
}
return this;
}
}
// Helps detecting changes in two array-states
export const arrayDiff = (next = [], current = [], callbackNew, callbackDeleted) => {
let nextSet = new Set(next);
let currentSet = new Set(current);
let promises = [];
if (callbackNew) {
let newItems = next.filter(item => !currentSet.has(item));
if (newItems.length) {
let results = callbackNew(newItems);
if (results) {
promises = results;
}
}
}
if (callbackDeleted) {
let deletedItems = current.filter(item => !nextSet.has(item));
if (deletedItems.length) {
let results = callbackDeleted(deletedItems);
if (results) {
promises = [...promises, ...results];
}
}
}
return Promise.all(promises);
};
// State observe with pre-select and user-select
export const createObserver = (store, preSelect = store.getState) => (userSelect, onChange, options = {}) => {
const {
fireOnStart = false, // trigger onChange immediately
unsubscribeOnUndefined = false, // unsubscribe if userSelect() returns undefined
onRemove = undefined, // callback when state changes to undefined
manager = undefined, // callback to pass unsubscribe function to
} = options;
let finish, unsubscribed = false;
let preselect, state;
if (
(preselect = preSelect()) === undefined ||
((state = userSelect(preselect)) === undefined && unsubscribeOnUndefined)
)
return () => {};
const handleChange = () => {
if (unsubscribed) return;
// Get pre-selected state
const previousState = state;
// Unsubscribe if preselect or state is undefined
if (
(preselect = preSelect()) === undefined ||
((state = userSelect(preselect)) === undefined && unsubscribeOnUndefined)
) {
// revert state so that finish calls onRemove on a good state
state = previousState;
return finish();
}
if (state === previousState) return;
onChange(state, previousState);
};
// Subscribe to store, fire on start, return hooked unsubscribe
if (fireOnStart) {
state = undefined; // so that onChange is called with prev === undefined
handleChange();
}
const unsubscribe = store.subscribe(handleChange);
finish = () => {
if (unsubscribed) return;
unsubscribed = true;
unsubscribe();
onRemove && onRemove(state, finish);
};
// Call manager, if available. This allows classes to track subscribes and clean up
// in bulk once they are destroyed.
if (manager) manager(finish);
return finish;
};
// Build ClassAD feature path (_O. ...)
export const buildFeaturePath = (tree, feature) => {
if (typeof feature !== 'object') feature = tree[feature];
let path = [normalizeName(feature.name)], parent = feature;
while (parent && parent.parent > 1) {
parent = tree[parent.parent];
path.push(normalizeName(parent.name));
}
return '_O.' + path.reverse().join('.');
};
export const normalizeName = name => {
return name.replace(/(\s|-|\r?\n)/g, '');
};