Skip to content

Instantly share code, notes, and snippets.

@jsdbroughton
Last active February 16, 2020 23:27
Show Gist options
  • Save jsdbroughton/524c4fac4804b42db2ebe24dafe1e072 to your computer and use it in GitHub Desktop.
Save jsdbroughton/524c4fac4804b42db2ebe24dafe1e072 to your computer and use it in GitHub Desktop.
Not as recursive as I'd hoped. Or how Javascript's essence saved me from recurring dreams.
import {AbstractMesh} from "webgl-library";
export interface ModelNode {
// values from WebGL
id: string;
uniqueId: number;
mesh: AbstractMesh;
// name for cross-referencing
name?: string;
// data fields used to structure the model
building: string;
floor: string;
zone: string;
//final leaf node id
mark: string;
}
export interface TreeItem {
id: number;
name: string;
parentKey?: string;
key?: string;
children?: TreeItem[];
// data object connects tree to model element
data?: ModelNode;
}
export interface FieldToken {
name: string;
// attributes used in UI
id?: number;
fix?: boolean;
}
import { FieldToken, TreeItem } from "../types.ts";
// basic string value sort by name values
const sortByName = ( { name:a }: TreeItem, { name:b }: TreeItem ):number =>
a == b ? 0 : a < b ? -1 : 1;
/**
* Nest a flat array of objects based on the precedence order of attributes.
* @param {TreeItem[]} treeItems - The flat object array.
* @param {FieldToken[]} fieldTokens - The ordered array of attributes.
* @return {TreeItem[]} The nested array.
*/
export const nestTreeByFieldNames = ( treeItems: TreeItem[], fieldTokens: FieldToken[] ): TreeItem[] => {
const uniqueIds: Set<number> = new Set();
let lastId = 1;
// All treeviewItems
treeItems.forEach( ( item ): void => {
if ( item && item.data ) {
uniqueIds.add( item.data.uniqueId );
lastId += 1;
}
} );
const fieldNames = fieldTokens.map( ( field ): string => field.name );
const parentKeys: Set<string> = new Set();
const keyedItems = treeItems.map( ( item ): TreeItem => {
const fieldValues = fieldNames.map( ( field ): string => item.data && item.data[field] );
let parentKey = fieldValues.slice( 0, -1 ).reduce( ( id, field ):string=>{
if ( id && field ) {
id += `-` + field;
}
if ( !id && field ) {
id = field;
}
parentKeys.add( id );
return id;
}, `` );
let key = fieldValues.join( `-` );
parentKeys.add( parentKey );
// return the TreeItem with correct key and parentKey values
// ditch the mesh component of the TreeItem as unneeded baggage
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const { mesh, ...keyedItem } = Object.assign( item.data, { id:item.id, name: item.name, parentKey, key } );
return keyedItem;
} );
const parentItems:TreeItem[] = [];
parentKeys.forEach( ( v:string ):void=>{
let parts = v.split( `-` );
while ( uniqueIds.has( lastId ) ) {
lastId += 1;
}
uniqueIds.add( lastId );
let name = parts.pop() || ``;
parentItems.push( {
id: lastId,
key: v,
name,
parentKey: parts.join( `-` )
} );
} );
let tree:TreeItem[] = [];
const keyMap:Map<string, number> = new Map();
const treeItemsWithBranches = [...keyedItems, ...parentItems];
treeItemsWithBranches.forEach( ( node ):void => {
if ( !node.parentKey ) {
tree.push( node );
tree = tree.sort( sortByName );
return;
}
let parentIndex = keyMap.get( node.parentKey );
if ( !keyMap.get( node.parentKey ) ) {
parentIndex = treeItemsWithBranches.findIndex( ( el ):boolean => el.key === node.parentKey );
keyMap.set( node.parentKey, parentIndex );
}
if ( parentIndex !== undefined && parentIndex >= 0 ) {
if ( !treeItemsWithBranches[parentIndex] || !treeItemsWithBranches[parentIndex].children ) {
treeItemsWithBranches[parentIndex].children = [node];
return;
}
treeItemsWithBranches[parentIndex]?.children?.push( node );
treeItemsWithBranches[parentIndex]?.children?.sort( sortByName );
}
} );
return tree;
};
<template>
<app>
<ul style="list-style: none;">
<draggable v-model="tokens" draggable=".drg">
<li v-for="token in tokens" :key="token.id" :class="{drg:!token.fix}">{{ token.name }}</li>
</draggable>
</ul>
<treeview
ref="selectionTree"
:items="itemTree"
selectable
@input="updateVisibleItems"
/>
<Model @loaded="modelLoaded">
</app>
</template>
<script lang="ts">
import Vue from "vue";
import { nestTreeByFieldNames } from "../utils/pivotMap";
import { ModelNode, TreeItem } from "../types/maps";
Vue.use( vb );
export default Vue.extend( {
name: `ModelView`,
components: { draggable: () => import( `vuedraggable` ) },
data () {
return {
tokens: [{ name:`building` }, { name: `floor` }, { name: `zone` }, { name: `mark`, fix:true }],
mapOfMeshes: new Map() as Map<number, AbstractMesh>
};
},
computed: {
treeItems (): TreeItem[] {
if ( !this.modelNodes ) return [];
const modelNodes: ModelNode[] = this.modelNodes.slice();
let lastId = 0;
const allItems: TreeItem[] = modelNodes.map( model => {
if ( model.uniqueId > lastId ) lastId = model.uniqueId;
return {
data: model,
name: model.mark,
id: model.uniqueId
};
} );
return allItems;
},
uniqueIds (): number[] {
if ( !this.treeItems ) return [];
const allItems: TreeItem[] = this.treeItems;
const uniqueIds: Set<number> = new Set();
allItems.forEach( item => {
if ( item && item.data ) uniqueIds.add( item.data.uniqueId );
} );
return Array.from( uniqueIds.values() );
},
itemTree (): TreeItem[] {
if ( this.treeItems.length > 0 ) {
return nestTreeByFieldNames( this.treeItems, this.tokens );
}
return [];
},
modelNodes (): ModelNode[] {
if ( Object.entries( this.meshes ).length == 0 ) return [];
const modelNodes = this.meshes.map( mesh => {
const [building, floor, zone, mark] = mesh.id.split( /[-#]/ );
return {
mesh,
building,
floor,
zone,
mark,
id: mesh.id,
uniqueId: mesh.uniqueId
};
} );
return modelNodes;
}
},
methods: {
updateVisibleItems ( items: number[] ): void {
const selected: Set<number> = new Set( items || [] );
const uniqueIds: Set<number> = new Set( this.uniqueIds || [] );
const complement = new Set(
Array.from( uniqueIds ).filter( ( uid: number ) => !selected.has( uid ) )
);
const intersection = new Set(
Array.from( uniqueIds ).filter( ( uid: number ) => selected.has( uid ) )
);
this.mapOfMeshes.forEach( ( model: AbstractMesh, k: number ) => {
if ( complement.has( k ) ) model.isVisible = false;
if ( intersection.has( k ) ) model.isVisible = true;
} );
},
async modelLoaded ( scene: Scene ): Promise<void> {
const assets = await loadPromise( `../models/`, `model.gltf`, scene );
assets.addAllToScene();
this.assets = assets;
this.meshes = assets.meshes.slice( 1 );
const mapOfMeshes: Map<number, AbstractMesh> = new Map();
this.meshes.forEach( ( model: AbstractMesh ) => {
model.isVisible = false;
mapOfMeshes.set( model.uniqueId, model );
} );
this.mapOfMeshes = mapOfMeshes;
this.scene = scene;
},
}
} );
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment