Skip to content

Commit

Permalink
feat(front): allow static elements when streaming
Browse files Browse the repository at this point in the history
  • Loading branch information
agviegas committed Aug 1, 2024
1 parent 272f5f1 commit 974e254
Show file tree
Hide file tree
Showing 4 changed files with 221 additions and 19 deletions.
2 changes: 1 addition & 1 deletion packages/front/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@thatopen/components-front",
"description": "Collection of frontend tools to author BIM apps.",
"version": "2.1.13",
"version": "2.1.14",
"author": "That Open Company",
"contributors": [
"Antonio Gonzalez Viegas (https://github.com/agviegas)",
Expand Down
31 changes: 27 additions & 4 deletions packages/front/src/fragments/Highlighter/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,16 +343,21 @@ export class Highlighter
}

for (const fragID in filtered) {
const fragment = fragments.list.get(fragID);
if (!fragment) {
continue;
}
if (!this.selection[name][fragID]) {
this.selection[name][fragID] = new Set<number>();
}
const itemIDs = fragmentIdMap[fragID];

for (const itemID of itemIDs) {
this.selection[name][fragID].add(itemID);
}

const fragment = fragments.list.get(fragID);
if (!fragment) {
continue;
}

for (const itemID of itemIDs) {
fragment.setColor(color, [itemID]);
}

Expand Down Expand Up @@ -436,6 +441,24 @@ export class Highlighter
this.onSetup.trigger(this);
}

/**
* Applies all the existing styles to the given fragments. Useful when combining the highlighter with streaming.
*
* @param fragments - The list of fragment to update.
*/
updateFragments(fragments: Iterable<FRAGS.Fragment>) {
for (const frag of fragments) {
for (const name in this.selection) {
const map = this.selection[name];
const ids = map[frag.id];
const color = this.colors.get(name);
if (ids && color) {
frag.setColor(color, ids);
}
}
}
}

private async zoomSelection(name: string) {
if (!this.config.world) {
throw new Error("No world found in config!");
Expand Down
79 changes: 73 additions & 6 deletions packages/front/src/fragments/IfcStreamer/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -396,9 +396,54 @@ export class IfcStreamer extends OBC.Component implements OBC.Disposable {
await this._fileCache.delete();
}

private async loadFoundGeometries(seen: {
[modelID: string]: Map<number, Set<number>>;
}) {
/**
* Sets or unsets the specified fragments as static. Static fragments are streamed once and then kept in memory.
*
* @param ids - The list of fragment IDs to make static.
* @param active - Whether these items should be static or not.
* @param culled - Whether these items should be culled or not. If undefined: active=true will set items as culled, while active=false will remove items from both the culled and unculled list.
*/
async setStatic(ids: Iterable<string>, active: boolean, culled?: boolean) {
const staticGeometries: { [modelID: string]: Set<number> } = {};

for (const id of ids) {
const found = this.fragIDData.get(id);
if (!found) {
console.log(`Item not found: ${id}.`);
continue;
}

const [group, geometryID] = found;
const modelID = group.uuid;

if (!staticGeometries[modelID]) {
staticGeometries[modelID] = new Set<number>();
}

staticGeometries[modelID].add(geometryID);
}

if (active) {
const seen: { [modelID: string]: Map<number, Set<number>> } = {};
for (const modelID in staticGeometries) {
const map = new Map<number, Set<number>>();
map.set(1, staticGeometries[modelID]);
seen[modelID] = map;
}
// Load the static geometries, but don't show them
await this.loadFoundGeometries(seen, false);
await this.culler.addStaticGeometries(staticGeometries, culled);
} else {
this.culler.removeStaticGeometries(staticGeometries, culled);
}
}

private async loadFoundGeometries(
seen: {
[modelID: string]: Map<number, Set<number>>;
},
visible = true,
) {
for (const modelID in seen) {
if (this._isDisposing) return;

Expand Down Expand Up @@ -518,8 +563,25 @@ export class IfcStreamer extends OBC.Component implements OBC.Disposable {
}
}

this.newFragment(group, geometryID, geom, transp, true, loaded);
this.newFragment(group, geometryID, geom, opaque, false, loaded);
this.newFragment(
group,
geometryID,
geom,
transp,
true,
loaded,
visible,
);

this.newFragment(
group,
geometryID,
geom,
opaque,
false,
loaded,
visible,
);
}
}

Expand Down Expand Up @@ -555,7 +617,10 @@ export class IfcStreamer extends OBC.Component implements OBC.Disposable {
throw new Error("Fragment group not found!");
}

if (!this._loadedFragments[modelID]) continue;
if (!this._loadedFragments[modelID]) {
continue;
}

const loadedFrags = this._loadedFragments[modelID];
const geometries = unseen[modelID];

Expand Down Expand Up @@ -608,6 +673,7 @@ export class IfcStreamer extends OBC.Component implements OBC.Disposable {
instances: StreamedInstance[],
transparent: boolean,
result: FRAG.Fragment[],
visible: boolean,
) {
if (instances.length === 0) return;
if (this._isDisposing) return;
Expand Down Expand Up @@ -635,6 +701,7 @@ export class IfcStreamer extends OBC.Component implements OBC.Disposable {

const material = transparent ? this._baseMaterialT : this._baseMaterial;
const fragment = new FRAG.Fragment(geometry, material, instances.length);
fragment.mesh.visible = visible;

fragment.id = fragID;
fragment.mesh.uuid = fragID;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {

boxes = new Map<number, FRAGS.Fragment>();

private _staticGeometries: {
culled: { [modelID: string]: Set<number> };
unculled: { [modelID: string]: Set<number> };
} = { culled: {}, unculled: {} };

private readonly _geometry: THREE.BufferGeometry;

private _material = new THREE.MeshBasicMaterial({
Expand All @@ -47,7 +52,7 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {

private _geometries = new Map<string, CullerBoundingBox>();
private _geometriesGroups = new Map<number, THREE.Group>();
private _foundGeometries = new Set<string>();
private _geometriesInMemory = new Set<string>();
private _intervalID: number | null = null;

private codes = new Map<number, Map<number, string>>();
Expand Down Expand Up @@ -110,6 +115,8 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {
}
this._geometries.clear();

this._staticGeometries = { culled: {}, unculled: {} };

this._geometry.dispose();
this._material.dispose();
this._modelIDIndex.clear();
Expand Down Expand Up @@ -274,7 +281,7 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {

this._modelIDIndex.delete(modelID);
this._indexModelID.delete(index);
this._foundGeometries.clear();
this._geometriesInMemory.clear();
}

addFragment(modelID: string, geometryID: number, frag: FRAGS.Fragment) {
Expand Down Expand Up @@ -421,6 +428,94 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {
}
}

async addStaticGeometries(
geometries: { [modelID: string]: Set<number> },
culled = true,
) {
const event = {
data: {
colors: new Map<string, number>(),
},
};
const dummyPixelValue = this.threshold + 1000;

for (const modelID in geometries) {
const modelKey = this._modelIDIndex.get(modelID);
if (modelKey === undefined) {
continue;
}
const map = this.codes.get(modelKey);
if (!map) {
continue;
}

const geometryIDs = geometries[modelID];

for (const geometryID of geometryIDs) {
const colorCode = map.get(geometryID);
if (!colorCode) {
continue;
}

const geometry = this._geometries.get(colorCode);
if (!geometry) {
continue;
}

geometry.exists = true;
if (!culled) {
// Static unculled geometries are always visible
geometry.hidden = false;
geometry.time = performance.now();
event.data.colors.set(colorCode, dummyPixelValue);
}

this._geometriesInMemory.add(colorCode);

const statics = culled
? this._staticGeometries.culled
: this._staticGeometries.unculled;

if (!statics[modelID]) {
statics[modelID] = new Set();
}

statics[modelID].add(geometryID);
}
}

if (!culled) {
// If unculled, we'll make these geometries visible by forcing its discovery
await this.handleWorkerMessage(event as any);
}
}

removeStaticGeometries(
geometries: { [modelID: string]: Set<number> },
culled?: boolean,
) {
const options: ("culled" | "unculled")[] = [];
if (culled === undefined) {
options.push("culled", "unculled");
} else if (culled === true) {
options.push("culled");
} else {
options.push("unculled");
}

for (const modelID in geometries) {
const geometryIDs = geometries[modelID];
for (const option of options) {
const set = this._staticGeometries[option][modelID];
if (set) {
for (const geometryID of geometryIDs) {
set.delete(geometryID);
}
}
}
}
}

private setGeometryVisibility(
geometry: CullerBoundingBox,
visible: boolean,
Expand Down Expand Up @@ -459,7 +554,7 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {
let viewWasUpdated = false;

// We can only lose geometries that were previously found
const lostGeometries = new Set(this._foundGeometries);
const lostGeometries = new Set(this._geometriesInMemory);

for (const [color, number] of colors) {
const geometry = this._geometries.get(color);
Expand All @@ -480,16 +575,16 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {
const modelID = this._indexModelID.get(geometry.modelIndex) as string;

if (exists) {
// Geometry was visible, and still is
// Geometry was present in memory, and still is, so show it
geometry.time = now;
if (!toShow[modelID]) {
toShow[modelID] = new Set();
}
toShow[modelID].add(geometry.geometryID);
this._foundGeometries.add(color);
this._geometriesInMemory.add(color);
viewWasUpdated = true;
} else {
// New geometry found
// New geometry found that is not in memory
if (!toLoad[modelID]) {
toLoad[modelID] = new Map();
}
Expand All @@ -501,7 +596,7 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {
}
const set = toLoad[modelID].get(number) as Set<number>;
set.add(geometry.geometryID);
this._foundGeometries.add(color);
this._geometriesInMemory.add(color);
viewWasUpdated = true;
}
}
Expand Down Expand Up @@ -533,15 +628,32 @@ export class GeometryCullerRenderer extends OBC.CullerRenderer {
) {
const modelID = this._indexModelID.get(geometry.modelIndex) as string;
const lostTime = now - geometry.time;

const { culled, unculled } = this._staticGeometries;

if (lostTime > this.maxLostTime) {
// This geometry was lost too long - delete it

// If it's any kind of static geometry, skip it
if (
culled[modelID]?.has(geometry.geometryID) ||
unculled[modelID]?.has(geometry.geometryID)
) {
return;
}

if (!toRemove[modelID]) {
toRemove[modelID] = new Set();
}
geometry.exists = false;
toRemove[modelID].add(geometry.geometryID);
this._foundGeometries.delete(color);
this._geometriesInMemory.delete(color);
} else if (lostTime > this.maxHiddenTime) {
// If it's an unculled static geometry, skip it
if (unculled[modelID]?.has(geometry.geometryID)) {
return;
}

// This geometry was lost for a while - hide it
if (!toHide[modelID]) {
toHide[modelID] = new Set();
Expand Down

0 comments on commit 974e254

Please sign in to comment.