From 8c14215d510f87f99a4b84f77f3569d5148b1332 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 24 Dec 2024 20:36:34 -0800 Subject: [PATCH 01/53] v1 insert new segment accepts type and index; does not yet accept custom object to represent segment and its constituent components; doesn't appear to apply child components correctly -- instead look at working code to create new street-segments from an object in street-segment.js to test, run on console: > document.querySelector('[managed-street]').components['managed-street'].insertSegment(3, 'bike-lane'); --- src/components/managed-street.js | 59 ++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 32bdcf0f..647fba03 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -53,6 +53,65 @@ AFRAME.registerComponent('managed-street', { // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); }, + // New method to insert a segment at a specific index + insertSegment: function (index, type) { + // Validate index + if (index < 0 || index > this.managedEntities.length) { + console.error('[managed-street] Invalid index for insertion:', index); + return; + } + + // Create new segment entity + const segmentEl = document.createElement('a-entity'); + + // Get default properties for this segment type from STREET.types + const defaultProps = window.STREET.types[type] || {}; + + // Set up basic segment properties + const segmentProps = { + type: type, + width: defaultProps.width || 3, // Default 3m width if not specified + length: this.data.length, + level: 0, + direction: 'outbound', + color: defaultProps.color || window.STREET.colors.white, + surface: defaultProps.surface || 'asphalt' + }; + + // Set all properties on the segment + segmentEl.setAttribute('street-segment', segmentProps); + + // Set the layer name for the segment + segmentEl.setAttribute('data-layer-name', `${type} • default`); + + // Insert the segment at the specified index in the DOM + if (index === this.managedEntities.length) { + this.el.appendChild(segmentEl); + } else { + const referenceNode = this.managedEntities[index]; + this.el.insertBefore(segmentEl, referenceNode); + } + + // Wait for the segment to be loaded + segmentEl.addEventListener('loaded', () => { + // Refresh the managed entities list + this.refreshManagedEntities(); + + // Update the total width + const totalWidth = this.managedEntities.reduce((sum, segment) => { + return sum + (segment.getAttribute('street-segment').width || 0); + }, 0); + this.el.setAttribute('managed-street', 'width', totalWidth); + + // Apply justification to reposition all segments + this.applyJustification(); + + // Update the dirt box + this.createOrUpdateJustifiedDirtBox(); + }); + + return segmentEl; + }, setupMutationObserver: function () { // Create mutation observer if (this.observer) { From ec60b6c2bb01746d8a151163d659f446e866e778 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 24 Dec 2024 21:23:17 -0800 Subject: [PATCH 02/53] accept a default segmentObject, remove striping --- src/components/managed-street.js | 49 +++++++++++++++++++++++++------- 1 file changed, 38 insertions(+), 11 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 647fba03..a112f5d4 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -53,8 +53,14 @@ AFRAME.registerComponent('managed-street', { // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); }, - // New method to insert a segment at a specific index - insertSegment: function (index, type) { + /** + * Inserts a new street segment at the specified index + * @param {number} index - The index at which to insert the new segment + * @param {string} type - The segment type (e.g., 'drive-lane', 'bike-lane') + * @param {Object} [segmentObject] - Optional configuration object for the segment + * @returns {Element} The created segment element + */ + insertSegment: function (index, type, segmentObject = null) { // Validate index if (index < 0 || index > this.managedEntities.length) { console.error('[managed-street] Invalid index for insertion:', index); @@ -67,22 +73,40 @@ AFRAME.registerComponent('managed-street', { // Get default properties for this segment type from STREET.types const defaultProps = window.STREET.types[type] || {}; - // Set up basic segment properties + // Set up basic segment properties, merging defaults with any provided custom properties const segmentProps = { type: type, - width: defaultProps.width || 3, // Default 3m width if not specified + width: segmentObject?.width || defaultProps.width || 3, length: this.data.length, - level: 0, - direction: 'outbound', - color: defaultProps.color || window.STREET.colors.white, - surface: defaultProps.surface || 'asphalt' + level: segmentObject?.level ?? defaultProps.level ?? 0, + direction: + segmentObject?.direction || defaultProps.direction || 'outbound', + color: + segmentObject?.color || + defaultProps.color || + window.STREET.colors.white, + surface: segmentObject?.surface || defaultProps.surface || 'asphalt' }; - // Set all properties on the segment + // Set the segment component with properties segmentEl.setAttribute('street-segment', segmentProps); // Set the layer name for the segment - segmentEl.setAttribute('data-layer-name', `${type} • default`); + const layerName = segmentObject?.name || `${type} • default`; + segmentEl.setAttribute('data-layer-name', layerName); + + // If custom segment object is provided, wait for segment to load then generate its components + if (segmentObject) { + segmentEl.addEventListener('loaded', () => { + // Use the generateComponentsFromSegmentObject method from street-segment component + const streetSegmentComponent = segmentEl.components['street-segment']; + if (streetSegmentComponent) { + streetSegmentComponent.generateComponentsFromSegmentObject( + segmentObject + ); + } + }); + } // Insert the segment at the specified index in the DOM if (index === this.managedEntities.length) { @@ -92,7 +116,7 @@ AFRAME.registerComponent('managed-street', { this.el.insertBefore(segmentEl, referenceNode); } - // Wait for the segment to be loaded + // Wait for the segment to be fully loaded segmentEl.addEventListener('loaded', () => { // Refresh the managed entities list this.refreshManagedEntities(); @@ -108,6 +132,9 @@ AFRAME.registerComponent('managed-street', { // Update the dirt box this.createOrUpdateJustifiedDirtBox(); + + // If we have a previous segment, check if we need to add stripe separators + // TODO: Check striping here in the future }); return segmentEl; From 1781d2b3baafc7d9c2d5ad919a2e0bab5acc04c7 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 25 Dec 2024 14:38:23 -0800 Subject: [PATCH 03/53] simplify dom insertion logic --- src/components/managed-street.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index a112f5d4..b0314953 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -109,12 +109,8 @@ AFRAME.registerComponent('managed-street', { } // Insert the segment at the specified index in the DOM - if (index === this.managedEntities.length) { - this.el.appendChild(segmentEl); - } else { - const referenceNode = this.managedEntities[index]; - this.el.insertBefore(segmentEl, referenceNode); - } + const referenceNode = this.managedEntities[index] ?? null; + this.el.insertBefore(segmentEl, referenceNode); // Wait for the segment to be fully loaded segmentEl.addEventListener('loaded', () => { From 00644039e4386dde7d3c2b6a1e69e3c3d29e02b9 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sat, 28 Dec 2024 11:11:32 +0100 Subject: [PATCH 04/53] add multi command to be able to undo a sequence of actions in one step --- .../lib/commands/ComponentAddCommand.js | 6 ++- .../lib/commands/ComponentRemoveCommand.js | 6 ++- src/editor/lib/commands/EntityCloneCommand.js | 6 ++- .../lib/commands/EntityCreateCommand.js | 17 +++++-- .../lib/commands/EntityRemoveCommand.js | 6 ++- .../lib/commands/EntityUpdateCommand.js | 6 ++- src/editor/lib/commands/MultiCommand.js | 50 +++++++++++++++++++ src/editor/lib/commands/index.js | 2 + 8 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 src/editor/lib/commands/MultiCommand.js diff --git a/src/editor/lib/commands/ComponentAddCommand.js b/src/editor/lib/commands/ComponentAddCommand.js index 11528b54..609619b5 100644 --- a/src/editor/lib/commands/ComponentAddCommand.js +++ b/src/editor/lib/commands/ComponentAddCommand.js @@ -19,7 +19,7 @@ export class ComponentAddCommand extends Command { this.value = payload.value; } - execute() { + execute(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { entity.setAttribute(this.component, this.value); @@ -28,10 +28,11 @@ export class ComponentAddCommand extends Command { component: this.component, value: this.value }); + nextCommandCallback?.(entity); } } - undo() { + undo(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { entity.removeAttribute(this.component); @@ -39,6 +40,7 @@ export class ComponentAddCommand extends Command { entity, component: this.component }); + nextCommandCallback?.(entity); } } } diff --git a/src/editor/lib/commands/ComponentRemoveCommand.js b/src/editor/lib/commands/ComponentRemoveCommand.js index 18f36717..6811892e 100644 --- a/src/editor/lib/commands/ComponentRemoveCommand.js +++ b/src/editor/lib/commands/ComponentRemoveCommand.js @@ -25,7 +25,7 @@ export class ComponentRemoveCommand extends Command { : structuredClone(entity.getDOMAttribute(payload.component)); } - execute() { + execute(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { entity.removeAttribute(this.component); @@ -33,10 +33,11 @@ export class ComponentRemoveCommand extends Command { entity, component: this.component }); + nextCommandCallback?.(entity); } } - undo() { + undo(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { entity.setAttribute(this.component, this.value); @@ -45,6 +46,7 @@ export class ComponentRemoveCommand extends Command { component: this.component, value: this.value }); + nextCommandCallback?.(entity); } } } diff --git a/src/editor/lib/commands/EntityCloneCommand.js b/src/editor/lib/commands/EntityCloneCommand.js index 97cfa922..f5583380 100644 --- a/src/editor/lib/commands/EntityCloneCommand.js +++ b/src/editor/lib/commands/EntityCloneCommand.js @@ -16,21 +16,23 @@ export class EntityCloneCommand extends Command { this.entityId = null; } - execute() { + execute(nextCommandCallback) { const entityToClone = document.getElementById(this.entityIdToClone); if (entityToClone) { const clone = cloneEntityImpl(entityToClone, this.entityId); this.entityId = clone.id; + nextCommandCallback?.(clone); return clone; } } - undo() { + undo(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { entity.parentNode.removeChild(entity); Events.emit('entityremoved', entity); this.editor.selectEntity(document.getElementById(this.entityIdToClone)); + nextCommandCallback?.(entity); } } } diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index c8ceea4b..5495aaa3 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -2,20 +2,30 @@ import Events from '../Events'; import { Command } from '../command.js'; import { createEntity } from '../entity.js'; +/** + * @param editor Editor + * @param definition Entity definition + * @param callback Optional callback to call after the entity is created, + * get as argument the created entity. + * @constructor + */ export class EntityCreateCommand extends Command { - constructor(editor, definition) { + constructor(editor, definition, callback = undefined) { super(editor); this.type = 'entitycreate'; this.name = 'Create Entity'; this.definition = definition; + this.callback = callback; this.entityId = null; } - execute() { + execute(nextCommandCallback) { let definition = this.definition; const callback = (entity) => { this.editor.selectEntity(entity); + this.callback?.(entity); + nextCommandCallback?.(entity); }; const parentEl = this.definition.parentEl ?? @@ -30,12 +40,13 @@ export class EntityCreateCommand extends Command { return entity; } - undo() { + undo(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { entity.parentNode.removeChild(entity); Events.emit('entityremoved', entity); this.editor.selectEntity(null); + nextCommandCallback?.(entity); } } } diff --git a/src/editor/lib/commands/EntityRemoveCommand.js b/src/editor/lib/commands/EntityRemoveCommand.js index f206d09e..d550e9df 100644 --- a/src/editor/lib/commands/EntityRemoveCommand.js +++ b/src/editor/lib/commands/EntityRemoveCommand.js @@ -16,7 +16,7 @@ export class EntityRemoveCommand extends Command { this.index = Array.from(this.parentEl.children).indexOf(entity); } - execute() { + execute(nextCommandCallback) { const closest = findClosestEntity(this.entity); // Keep a clone not attached to DOM for undo @@ -31,9 +31,10 @@ export class EntityRemoveCommand extends Command { this.entity = clone; this.editor.selectEntity(closest); + nextCommandCallback?.(null); } - undo() { + undo(nextCommandCallback) { // Reinsert the entity at its original position using the stored index const referenceNode = this.parentEl.children[this.index] ?? null; this.parentEl.insertBefore(this.entity, referenceNode); @@ -44,6 +45,7 @@ export class EntityRemoveCommand extends Command { () => { Events.emit('entitycreated', this.entity); this.editor.selectEntity(this.entity); + nextCommandCallback?.(this.entity); }, { once: true } ); diff --git a/src/editor/lib/commands/EntityUpdateCommand.js b/src/editor/lib/commands/EntityUpdateCommand.js index 5361eb80..bab7535f 100644 --- a/src/editor/lib/commands/EntityUpdateCommand.js +++ b/src/editor/lib/commands/EntityUpdateCommand.js @@ -87,7 +87,7 @@ export class EntityUpdateCommand extends Command { } } - execute() { + execute(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { if (this.editor.selectedEntity && this.editor.selectedEntity !== entity) { @@ -119,10 +119,11 @@ export class EntityUpdateCommand extends Command { if (this.component === 'id') { this.entityId = this.newValue; } + nextCommandCallback?.(entity); } } - undo() { + undo(nextCommandCallback) { const entity = document.getElementById(this.entityId); if (entity) { if (this.editor.selectedEntity && this.editor.selectedEntity !== entity) { @@ -149,6 +150,7 @@ export class EntityUpdateCommand extends Command { if (this.component === 'id') { this.entityId = this.oldValue; } + nextCommandCallback?.(entity); } } diff --git a/src/editor/lib/commands/MultiCommand.js b/src/editor/lib/commands/MultiCommand.js new file mode 100644 index 00000000..627d4e8b --- /dev/null +++ b/src/editor/lib/commands/MultiCommand.js @@ -0,0 +1,50 @@ +import { Command } from '../command.js'; +import { commandsByType } from './index.js'; + +/** + * @param editor Editor + * @param commands + * @param callback Optional callback to call after all commands are executed, + * get as argument the created entity or null if last command is entityremove. + * @constructor + */ +export class MultiCommand extends Command { + constructor(editor, commands, callback = undefined) { + super(editor); + + this.type = 'multi'; + this.name = 'Multiple changes'; + this.updatable = false; + this.callback = callback; + this.commands = commands + .map((cmdTuple) => { + const Cmd = commandsByType.get(cmdTuple[0]); + if (!Cmd) { + console.error(`Command ${cmdTuple[0]} not found`); + return null; + } + return new Cmd(editor, cmdTuple[1], cmdTuple[2]); + }) + .filter(Boolean); + } + + execute() { + const run = this.commands + .toReversed() + .reduce((nextCommandCallback, command) => { + return (entityIgnored) => { + return command.execute(nextCommandCallback); + }; + }, this.callback); // latest callback uses the entity as parameter + return run(); + } + + undo() { + const run = this.commands.reduce((nextCommandCallback, command) => { + return (entityIgnored) => { + return command.undo(nextCommandCallback); + }; + }, this.callback); // latest callback uses the entity as parameter + return run(); + } +} diff --git a/src/editor/lib/commands/index.js b/src/editor/lib/commands/index.js index 2f1a6afb..1efeb43e 100644 --- a/src/editor/lib/commands/index.js +++ b/src/editor/lib/commands/index.js @@ -4,6 +4,7 @@ import { EntityCloneCommand } from './EntityCloneCommand.js'; import { EntityCreateCommand } from './EntityCreateCommand.js'; import { EntityRemoveCommand } from './EntityRemoveCommand.js'; import { EntityUpdateCommand } from './EntityUpdateCommand.js'; +import { MultiCommand } from './MultiCommand.js'; export const commandsByType = new Map(); commandsByType.set('componentadd', ComponentAddCommand); @@ -12,3 +13,4 @@ commandsByType.set('entityclone', EntityCloneCommand); commandsByType.set('entitycreate', EntityCreateCommand); commandsByType.set('entityremove', EntityRemoveCommand); commandsByType.set('entityupdate', EntityUpdateCommand); +commandsByType.set('multi', MultiCommand); From f32d1cbd8b49129fc0150a86dfbd5127c28393e6 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sat, 28 Dec 2024 12:28:48 +0100 Subject: [PATCH 05/53] allow definition.parentEl to be a string representing the id of the parent entity --- src/editor/lib/commands/EntityCreateCommand.js | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/editor/lib/commands/EntityCreateCommand.js b/src/editor/lib/commands/EntityCreateCommand.js index 5495aaa3..d7ab79d0 100644 --- a/src/editor/lib/commands/EntityCreateCommand.js +++ b/src/editor/lib/commands/EntityCreateCommand.js @@ -27,9 +27,17 @@ export class EntityCreateCommand extends Command { this.callback?.(entity); nextCommandCallback?.(entity); }; - const parentEl = - this.definition.parentEl ?? - document.querySelector(this.editor.config.defaultParent); + let parentEl; + if (this.definition.parentEl) { + if (typeof this.definition.parentEl === 'string') { + parentEl = document.getElementById(this.definition.parentEl); + } else { + parentEl = this.definition.parentEl; + } + } + if (!parentEl) { + parentEl = document.querySelector(this.editor.config.defaultParent); + } // If we undo and redo, use the previous id so next redo actions (for example entityupdate to move the position) works correctly if (this.entityId) { definition = { ...this.definition, id: this.entityId }; From 252b8086ce8fff95cf98dad70e840cde35f28164 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Sat, 28 Dec 2024 12:46:43 +0100 Subject: [PATCH 06/53] add callback param to inspector.execute --- src/editor/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/editor/index.js b/src/editor/index.js index a91149ff..a5c8c866 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -206,13 +206,13 @@ Inspector.prototype = { }); }, - execute: function (cmdName, payload, optionalName) { + execute: function (cmdName, payload, optionalName, callback = undefined) { const Cmd = commandsByType.get(cmdName); if (!Cmd) { console.error(`Command ${cmdName} not found`); return; } - return this.history.execute(new Cmd(this, payload), optionalName); + return this.history.execute(new Cmd(this, payload, callback), optionalName); }, undo: function () { From 1fd94a079aea1f90837280aa87432bb5cb7746ff Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 28 Dec 2024 12:02:47 -0800 Subject: [PATCH 07/53] appears to work great as a user i can detach a clone and the action is undo-able. if i save and reload I cannot undo or reattach the component unless i choose to add a new clone component or reset the segment completely. --- src/components/street-generated-clones.js | 35 +++++++++++ src/editor/components/components/Sidebar.js | 64 ++++++++++++++++++++- src/editor/icons/icons.jsx | 60 +++++++++++++++++++ 3 files changed, 158 insertions(+), 1 deletion(-) diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index b29d5e0e..f3251936 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -41,6 +41,39 @@ AFRAME.registerComponent('street-generated-clones', { this.createdEntities.length = 0; // Clear the array }, + detach: function () { + const commands = []; + commands.push([ + 'componentremove', + { entity: this.el, component: this.attrName } + ]); + let entityObjToPushAtTheEnd = null; // so that the entity is selected after executing the multi command + this.createdEntities.forEach((entity) => { + const position = entity.getAttribute('position'); + const rotation = entity.getAttribute('rotation'); + const entityObj = { + parentEl: this.el, // you can also put this.el.id here that way the command is fully json serializable but el currently doesn't have an id + mixin: entity.getAttribute('mixin'), + 'data-layer-name': entity + .getAttribute('data-layer-name') + .replace('Cloned Model', 'Detached Model'), + components: { + position: { x: position.x, y: position.y, z: position.z }, + rotation: { x: rotation.x, y: rotation.y, z: rotation.z } + } + }; + if (AFRAME.INSPECTOR?.selectedEntity === entity) { + entityObjToPushAtTheEnd = entityObj; + } else { + commands.push(['entitycreate', entityObj]); + } + }); + if (entityObjToPushAtTheEnd !== null) { + commands.push(['entitycreate', entityObjToPushAtTheEnd]); + } + AFRAME.INSPECTOR.execute('multi', commands); + }, + update: function (oldData) { // If mode is random or randomFacing and seed is 0, generate a random seed and return, // the update will be called again because of the setAttribute. @@ -56,6 +89,7 @@ AFRAME.registerComponent('street-generated-clones', { // Clear existing entities this.remove(); + this.createdEntities = []; // Generate new entities based on mode switch (this.data.mode) { @@ -137,6 +171,7 @@ AFRAME.registerComponent('street-generated-clones', { clone.classList.add('autocreated'); clone.setAttribute('data-no-transform', ''); clone.setAttribute('data-layer-name', 'Cloned Model • ' + mixinId); + clone.setAttribute('data-parent-component', this.attrName); this.el.appendChild(clone); this.createdEntities.push(clone); diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index 4d460c6a..bc3395a7 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -15,7 +15,10 @@ import { ArrowRightIcon, Object24Icon, SegmentIcon, - ManagedStreetIcon + ManagedStreetIcon, + AutoIcon, + ManualIcon, + ArrowLeftHookIcon } from '../../icons'; import GeoSidebar from './GeoSidebar'; // Make sure to create and import this new component import IntersectionSidebar from './IntersectionSidebar'; @@ -35,6 +38,27 @@ export default class Sidebar extends React.Component { }; } + getParentComponentName = (entity) => { + const componentName = entity.getAttribute('data-parent-component'); + const parentEntity = entity.parentElement; + return componentName + ? `${parentEntity.getAttribute('data-layer-name') || 'Entity'}:${componentName}` + : 'Unknown'; + }; + + // entity.getAttribute('data-parent-component') + fireParentComponentDetach = (entity) => { + const componentName = entity.getAttribute('data-parent-component'); + const parentEntity = entity.parentElement; + parentEntity.components[componentName].detach(); + // reselect the entity to refresh the sidepanel + // AFRAME.INSPECTOR.selectEntity(entity); + }; + + selectParentEntity = (entity) => { + AFRAME.INSPECTOR.selectEntity(entity.parentElement); + }; + onEntityUpdate = (detail) => { if (detail.entity !== this.props.entity) { return; @@ -115,6 +139,44 @@ export default class Sidebar extends React.Component { {entity.id !== 'reference-layers' && !entity.getAttribute('street-segment') ? ( <> + {entity.classList.contains('autocreated') && ( + <> +
+
+ +
+ Autocreated Clone +
+
+
+ + +
+
+ + + )} {!!entity.mixinEls.length && } {entity.hasAttribute('data-no-transform') ? ( <> diff --git a/src/editor/icons/icons.jsx b/src/editor/icons/icons.jsx index e5559ae2..10d15a79 100644 --- a/src/editor/icons/icons.jsx +++ b/src/editor/icons/icons.jsx @@ -1,3 +1,63 @@ +export const ArrowLeftHookIcon = () => ( + + + +); + +export const ManualIcon = () => ( + + + + +); + +export const AutoIcon = () => ( + + + + +); export const Camera32Icon = () => ( Date: Sat, 28 Dec 2024 12:03:52 -0800 Subject: [PATCH 08/53] not needed --- src/components/street-generated-clones.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index f3251936..419f604b 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -89,7 +89,6 @@ AFRAME.registerComponent('street-generated-clones', { // Clear existing entities this.remove(); - this.createdEntities = []; // Generate new entities based on mode switch (this.data.mode) { From 7f9d817e330eda02acddab961aa919afda365d3f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 28 Dec 2024 12:08:44 -0800 Subject: [PATCH 09/53] hide mixin widget for attached clones --- src/editor/components/components/Sidebar.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index bc3395a7..ccb692d8 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -51,8 +51,6 @@ export default class Sidebar extends React.Component { const componentName = entity.getAttribute('data-parent-component'); const parentEntity = entity.parentElement; parentEntity.components[componentName].detach(); - // reselect the entity to refresh the sidepanel - // AFRAME.INSPECTOR.selectEntity(entity); }; selectParentEntity = (entity) => { @@ -177,7 +175,10 @@ export default class Sidebar extends React.Component { )} - {!!entity.mixinEls.length && } + {!!entity.mixinEls.length && + !entity.classList.contains('autocreated') && ( + + )} {entity.hasAttribute('data-no-transform') ? ( <> ) : ( From a6add453f088a6a539f15abb33f3665288d64870 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 28 Dec 2024 12:13:51 -0800 Subject: [PATCH 10/53] allow ped and stencil detach --- .../street-generated-pedestrians.js | 35 ++++++++++++++++++- src/components/street-generated-stencil.js | 33 +++++++++++++++++ 2 files changed, 67 insertions(+), 1 deletion(-) diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js index 5fef3cb6..4c99aefb 100644 --- a/src/components/street-generated-pedestrians.js +++ b/src/components/street-generated-pedestrians.js @@ -45,6 +45,38 @@ AFRAME.registerComponent('street-generated-pedestrians', { this.createdEntities.forEach((entity) => entity.remove()); this.createdEntities.length = 0; }, + detach: function () { + const commands = []; + commands.push([ + 'componentremove', + { entity: this.el, component: this.attrName } + ]); + let entityObjToPushAtTheEnd = null; // so that the entity is selected after executing the multi command + this.createdEntities.forEach((entity) => { + const position = entity.getAttribute('position'); + const rotation = entity.getAttribute('rotation'); + const entityObj = { + parentEl: this.el, // you can also put this.el.id here that way the command is fully json serializable but el currently doesn't have an id + mixin: entity.getAttribute('mixin'), + 'data-layer-name': entity + .getAttribute('data-layer-name') + .replace('Cloned Pedestrian', 'Detached Pedestrian'), + components: { + position: { x: position.x, y: position.y, z: position.z }, + rotation: { x: rotation.x, y: rotation.y, z: rotation.z } + } + }; + if (AFRAME.INSPECTOR?.selectedEntity === entity) { + entityObjToPushAtTheEnd = entityObj; + } else { + commands.push(['entitycreate', entityObj]); + } + }); + if (entityObjToPushAtTheEnd !== null) { + commands.push(['entitycreate', entityObjToPushAtTheEnd]); + } + AFRAME.INSPECTOR.execute('multi', commands); + }, update: function (oldData) { const data = this.data; @@ -109,7 +141,8 @@ AFRAME.registerComponent('street-generated-pedestrians', { // Add metadata pedestrian.classList.add('autocreated'); pedestrian.setAttribute('data-no-transform', ''); - pedestrian.setAttribute('data-layer-name', 'Generated Pedestrian'); + pedestrian.setAttribute('data-layer-name', 'Cloned Pedestrian'); + pedestrian.setAttribute('data-parent-component', this.attrName); this.createdEntities.push(pedestrian); } diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index b78e3396..47b2399f 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -99,6 +99,38 @@ AFRAME.registerComponent('street-generated-stencil', { this.createdEntities.forEach((entity) => entity.remove()); this.createdEntities.length = 0; // Clear the array }, + detach: function () { + const commands = []; + commands.push([ + 'componentremove', + { entity: this.el, component: this.attrName } + ]); + let entityObjToPushAtTheEnd = null; // so that the entity is selected after executing the multi command + this.createdEntities.forEach((entity) => { + const position = entity.getAttribute('position'); + const rotation = entity.getAttribute('rotation'); + const entityObj = { + parentEl: this.el, // you can also put this.el.id here that way the command is fully json serializable but el currently doesn't have an id + mixin: entity.getAttribute('mixin'), + 'data-layer-name': entity + .getAttribute('data-layer-name') + .replace('Cloned Model', 'Detached Model'), + components: { + position: { x: position.x, y: position.y, z: position.z }, + rotation: { x: rotation.x, y: rotation.y, z: rotation.z } + } + }; + if (AFRAME.INSPECTOR?.selectedEntity === entity) { + entityObjToPushAtTheEnd = entityObj; + } else { + commands.push(['entitycreate', entityObj]); + } + }); + if (entityObjToPushAtTheEnd !== null) { + commands.push(['entitycreate', entityObjToPushAtTheEnd]); + } + AFRAME.INSPECTOR.execute('multi', commands); + }, update: function (oldData) { const data = this.data; @@ -166,6 +198,7 @@ AFRAME.registerComponent('street-generated-stencil', { clone.classList.add('autocreated'); clone.setAttribute('data-no-transform', ''); clone.setAttribute('data-layer-name', `Cloned Model • ${stencilName}`); + clone.setAttribute('data-parent-component', this.attrName); this.el.appendChild(clone); this.createdEntities.push(clone); From 2a84143f3910d2d7ba3c00f7da9bc6c027eb8bd6 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 28 Dec 2024 21:37:37 -0800 Subject: [PATCH 11/53] striping first cut --- src/components/managed-street.js | 82 ++++++++++++++++++++- src/components/street-generated-striping.js | 14 +++- src/components/street-segment.js | 13 ++++ 3 files changed, 106 insertions(+), 3 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index b0314953..58d54add 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -334,6 +334,7 @@ AFRAME.registerComponent('managed-street', { for (let i = 0; i < streetObject.segments.length; i++) { const segment = streetObject.segments[i]; + const previousSegment = streetObject.segments[i - 1]; const segmentEl = document.createElement('a-entity'); this.el.appendChild(segmentEl); @@ -349,6 +350,27 @@ AFRAME.registerComponent('managed-street', { segmentEl.setAttribute('data-layer-name', segment.name); // wait for street-segment to be loaded, then generate components from segment object segmentEl.addEventListener('loaded', () => { + if (!segment.generated?.striping) { + console.log('segment generated striping not exists'); + const stripingVariant = this.getStripingFromSegments( + previousSegment, + segment + ); + console.log('stripingVariant', stripingVariant); + if (stripingVariant) { + // Only add striping if variant is not null + if (!segment.generated) { + segment.generated = {}; + } + segment.generated.striping = [ + { + striping: stripingVariant, + length: streetObject.length, + segmentWidth: segment.width + } + ]; + } + } segmentEl.components[ 'street-segment' ].generateComponentsFromSegmentObject(segment); @@ -356,6 +378,64 @@ AFRAME.registerComponent('managed-street', { }); } }, + getStripingFromSegments: function (previousSegment, currentSegment) { + if (!previousSegment || !currentSegment) { + return null; + } + + // Valid lane types that should have striping + const validLaneTypes = [ + 'drive-lane', + 'bus-lane', + 'bike-lane', + 'parking-lane' + ]; + + // Only add striping between valid lane types + if ( + !validLaneTypes.includes(previousSegment.type) || + !validLaneTypes.includes(currentSegment.type) + ) { + return null; + } + + // Default to solid line + let variantString = 'solid-stripe'; + + // Check for opposite directions + if ( + previousSegment.direction !== currentSegment.direction && + previousSegment.direction !== 'none' && + currentSegment.direction !== 'none' + ) { + variantString = 'solid-doubleyellow'; + + // Special case for bike lanes + if ( + currentSegment.type === 'bike-lane' && + previousSegment.type === 'bike-lane' + ) { + variantString = 'short-dashed-stripe-yellow'; + } + } else { + // Same direction cases + if (currentSegment.type === previousSegment.type) { + variantString = 'dashed-stripe'; + } + + // Drive lane and turn lane combination would go here if needed + } + + // Special case for parking lanes - use dashed line between parking and drive lanes + if ( + currentSegment.type === 'parking-lane' || + previousSegment.type === 'parking-lane' + ) { + variantString = 'solid-stripe'; + } + + return variantString; + }, loadAndParseStreetmixURL: async function (streetmixURL) { const data = this.data; const streetmixAPIURL = streetmixUtils.streetmixUserToAPI(streetmixURL); @@ -538,7 +618,7 @@ function getSeparatorMixinId(previousSegment, currentSegment) { currentSegment.type === 'parking-lane' || previousSegment.type === 'parking-lane' ) { - variantString = 'invisible'; + variantString = 'solid-stripe'; } return variantString; diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 8714eb72..bff0279f 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -7,7 +7,17 @@ AFRAME.registerComponent('street-generated-striping', { multiple: true, schema: { striping: { - type: 'string' + type: 'string', + oneOf: [ + 'none', + 'solid-stripe', + 'dashed-stripe', + 'short-dashed-stripe', + 'short-dashed-stripe-yellow', + 'solid-doubleyellow', + 'solid-dashed', + 'solid-dashed-yellow' + ] }, segmentWidth: { type: 'number' @@ -43,7 +53,7 @@ AFRAME.registerComponent('street-generated-striping', { // Clean up old entities this.remove(); - if (data.striping === 'invisible') { + if (!data.striping || data.striping === 'none') { return; } const clone = document.createElement('a-entity'); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index ecac7345..01892291 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -252,6 +252,19 @@ AFRAME.registerComponent('street-segment', { }); }); } + + if (componentsToGenerate?.striping?.length > 0) { + componentsToGenerate.striping.forEach((stripe, index) => { + this.el.setAttribute(`street-generated-striping__${index}`, { + striping: stripe.striping, + segmentWidth: this.data.width, + length: this.data.length, + positionY: stripe.positionY || 0.05, // Default to 0.05 if not specified + side: stripe.side || 'left', // Default to left if not specified + facing: stripe.facing || 0 // Default to 0 if not specified + }); + }); + } }, updateSurfaceFromType: function (typeObject) { // update color, surface, level from segment type preset From 5a44900d6f7b18af7813d2de2cee76eaba8db173 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 28 Dec 2024 22:13:02 -0800 Subject: [PATCH 12/53] remove logging --- src/components/managed-street.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 58d54add..5eec3f9e 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -351,12 +351,10 @@ AFRAME.registerComponent('managed-street', { // wait for street-segment to be loaded, then generate components from segment object segmentEl.addEventListener('loaded', () => { if (!segment.generated?.striping) { - console.log('segment generated striping not exists'); const stripingVariant = this.getStripingFromSegments( previousSegment, segment ); - console.log('stripingVariant', stripingVariant); if (stripingVariant) { // Only add striping if variant is not null if (!segment.generated) { From 080e24d04e8f67fe8c748e4de3e3ed862c1cfe1a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 29 Dec 2024 12:17:55 -0800 Subject: [PATCH 13/53] use ?? instead of || for defaults --- src/components/street-segment.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 01892291..82b31e2c 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -259,9 +259,9 @@ AFRAME.registerComponent('street-segment', { striping: stripe.striping, segmentWidth: this.data.width, length: this.data.length, - positionY: stripe.positionY || 0.05, // Default to 0.05 if not specified - side: stripe.side || 'left', // Default to left if not specified - facing: stripe.facing || 0 // Default to 0 if not specified + positionY: stripe.positionY ?? 0.05, // Default to 0.05 if not specified + side: stripe.side ?? 'left', // Default to left if not specified + facing: stripe.facing ?? 0 // Default to 0 if not specified }); }); } From fea07d2de01cf9eefadb62f9fc6233894fe3039c Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 00:11:35 -0800 Subject: [PATCH 14/53] barely working streetplan --- src/components/managed-street.js | 95 ++++++++++++++++++- .../AddLayerPanel/createLayerFunctions.js | 26 +++++ .../components/AddLayerPanel/layersData.js | 12 ++- 3 files changed, 130 insertions(+), 3 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 32bdcf0f..73b75cda 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -122,8 +122,7 @@ AFRAME.registerComponent('managed-street', { if (data.sourceType === 'streetmix-url') { this.loadAndParseStreetmixURL(data.sourceValue); } else if (data.sourceType === 'streetplan-url') { - // this function is not yet implemented - this.refreshFromStreetplanURL(data.sourceValue); + this.loadAndParseStreetplanURL(data.sourceValue); } else if (data.sourceType === 'json-blob') { // if data.sourceValue is a string convert string to object for parsing but keep string for saving if (typeof data.sourceValue === 'string') { @@ -274,6 +273,98 @@ AFRAME.registerComponent('managed-street', { }); } }, + loadAndParseStreetplanURL: async function (streetplanURL) { + console.log( + '[managed-street] loader', + 'sourceType: `streetplan-url`, loading from', + streetplanURL + ); + + try { + const response = await fetch(streetplanURL); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const streetplanData = await response.json(); + const boulevard = streetplanData.project['My Street']['Boulevard Alt 1']; + + // Convert StreetPlan format to managed-street format + const streetObject = { + name: streetplanData.project.ProjectName, + width: 0, // Will be calculated from segments + length: + parseFloat(streetplanData.project['My Street'].LengthMiles) * + 5280 * + 0.3048, // Convert miles to meters + segments: [] + }; + + // Process segments + const segments = boulevard.segments; + for (const segmentKey in segments) { + const segment = segments[segmentKey]; + const segmentWidth = parseFloat(segment.width) * 0.3048; // Convert feet to meters + streetObject.width += segmentWidth; + + // Convert segment type based on your schema + let segmentType = 'drive-lane'; // Default type + let segmentDirection = 'inbound'; + let segmentColor = window.STREET.colors.white; + + switch (segment.Type) { + case 'BikesPaths': + segmentType = 'bike-lane'; + break; + case 'Walkways': + segmentType = 'sidewalk'; + break; + case 'Transit': + segmentType = 'bus-lane'; + segmentColor = window.STREET.colors.red; + break; + case 'Median/Buffer': + segmentType = 'divider'; + break; + case 'Curbside': + segmentType = 'divider'; + break; + case 'Gutter': + segmentType = 'parking-lane'; + break; + case 'Furniture': + segmentType = 'sidewalk-tree'; + break; + // Add more type mappings as needed + } + + // Determine direction based on segment data + if (segment.Direction === 'Coming') { + segmentDirection = 'inbound'; + } else if (segment.Direction === 'Going') { + segmentDirection = 'outbound'; + } + + streetObject.segments.push({ + type: segmentType, + width: segmentWidth, + name: segment.title, + level: parseFloat(segment.MaterialH) || 0, + direction: segmentDirection, + color: segmentColor, + surface: segment.Material?.toLowerCase() || 'asphalt' + }); + } + + // Parse the street object + this.parseStreetObject(streetObject); + } catch (error) { + console.error('[managed-street] loader', 'Loading Error:', error); + STREET.notify.warningMessage( + 'Error loading StreetPlan data: ' + error.message + ); + } + }, loadAndParseStreetmixURL: async function (streetmixURL) { const data = this.data; const streetmixAPIURL = streetmixUtils.streetmixUserToAPI(streetmixURL); diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index eed592dd..a163e4d1 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -77,6 +77,32 @@ export function createManagedStreetFromStreetmixURLPrompt(position) { } } +export function createManagedStreetFromStreetplanURLPrompt(position) { + // This creates a new Managed Street + let streetplanURL = prompt( + 'Please enter a StreetPlan URL', + 'https://streetplan.net/3dstreet/89474' + ); + + if (streetplanURL && streetplanURL !== '') { + const definition = { + id: createUniqueId(), + components: { + position: position ?? '0 0 0', + 'managed-street': { + sourceType: 'streetplan-url', + sourceValue: streetplanURL, + showVehicles: true, + showStriping: true, + synchronize: true + } + } + }; + + AFRAME.INSPECTOR.execute('entitycreate', definition); + } +} + export function createManagedStreetFromStreetObject(position, streetObject) { // This creates a new Managed Street if (streetObject && streetObject !== '') { diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index c20b2b37..f69e49ba 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -70,12 +70,22 @@ export const streetLayersData = [ id: 8, handlerFunction: createFunctions.createManagedStreetFromStreetmixURLPrompt }, + { + name: '(Beta) Managed Street from Streetplan URL', + img: '', + requiresPro: true, + icon: '', + description: + 'Create a new street from Streetplan URL using the Managed Street component.', + id: 9, + handlerFunction: createFunctions.createManagedStreetFromStreetplanURLPrompt + }, { name: '(Beta) Managed Street 60ft RoW / 36ft Roadway Width', img: 'ui_assets/cards/street-preset-60-36.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', - id: 9, + id: 10, handlerFunction: createFunctions.create60ftRightOfWayManagedStreet } ]; From 0bf47bbc1a7a86031ac589e1ecdc2791c16f35c4 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 11:00:24 -0800 Subject: [PATCH 15/53] Managed Street version of 40-ft stroad Co-Authored-By: Ben Kovitz --- .../AddLayerPanel/createLayerFunctions.js | 11 ++ .../AddLayerPanel/defaultStreets.js | 158 ++++++++++++++++++ .../components/AddLayerPanel/layersData.js | 8 + 3 files changed, 177 insertions(+) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index eed592dd..cb3e4c72 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -151,6 +151,17 @@ export function create60ftRightOfWayManagedStreet(position) { ); } +export function create40ftRightOfWayManagedStreet(position) { + console.log( + 'create40ftRightOfWayManagedStreet', + defaultStreetObjects.stroad40ftROW + ); + createManagedStreetFromStreetObject( + position, + defaultStreetObjects.stroad40ftROW + ); +} + export function create80ftRightOfWay(position) { createStreetmixStreet( position, diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js index 098f9f30..9dd37f11 100644 --- a/src/editor/components/components/AddLayerPanel/defaultStreets.js +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -322,3 +322,161 @@ export const exampleStreet = { } ] }; + +export const stroad40ftROW = { + id: '727dbbf4-692a-48ee-8f99-a056fd60fedd', + name: '40ft Right of Way 24ft Road Width', + width: 12.192, // Original 40ft converted to meters + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'JCWzsLQHmyfDHzQhi9_pU', + name: 'Dense Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, // Original 6ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'dense' + } + ] + } + }, + { + id: 'RsLZFtSi3oJH7uufQ5rc4', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.61, // Original 2ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'GbEHhCMPmVom_IJK-xIn3', + name: 'Inbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.134, // Original 7ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'z4gZgzYoM7sQ7mzIV01PC', + name: 'Drive Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'ARosTXeWGXp17QyfZgSKB', + name: 'Outbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.134, // Original 7ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'vL9qDNp5neZt32zlZ9ExG', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.61, // Original 2ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'RClRRZoof9_BYnqQm7mz-', + name: 'Normal Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, // Original 6ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + } + ] +}; diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index c20b2b37..d6ffa19a 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -77,6 +77,14 @@ export const streetLayersData = [ description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', id: 9, handlerFunction: createFunctions.create60ftRightOfWayManagedStreet + }, + { + name: '(Beta) Managed Street 40ft RoW / 24ft Roadway Width', + img: 'ui_assets/cards/street-preset-40-24.jpg', + icon: 'ui_assets/cards/icons/3dst24.png', + description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', + id: 10, + handlerFunction: createFunctions.create40ftRightOfWayManagedStreet } ]; From 9cc90d514ae89152ca7d1abec637ee113efe6f68 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 11:19:45 -0800 Subject: [PATCH 16/53] Managed Street format 80-foot stroad Co-Authored-By: Ben Kovitz --- .../AddLayerPanel/createLayerFunctions.js | 11 + .../AddLayerPanel/defaultStreets.js | 261 ++++++++++++++++++ .../components/AddLayerPanel/layersData.js | 8 + 3 files changed, 280 insertions(+) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index cb3e4c72..6fb73f51 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -162,6 +162,17 @@ export function create40ftRightOfWayManagedStreet(position) { ); } +export function create80ftRightOfWayManagedStreet(position) { + console.log( + 'create80ftRightOfWayManagedStreet', + defaultStreetObjects.stroad80ftROW + ); + createManagedStreetFromStreetObject( + position, + defaultStreetObjects.stroad80ftROW + ); +} + export function create80ftRightOfWay(position) { createStreetmixStreet( position, diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js index 9dd37f11..ac175e78 100644 --- a/src/editor/components/components/AddLayerPanel/defaultStreets.js +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -480,3 +480,264 @@ export const stroad40ftROW = { } ] }; + +export const stroad80ftROW = { + id: 'dea1980d-2a13-481b-b318-9f757ca114f7', + name: '80ft Right of Way 56ft Road Width', + width: 24.384, // Original 80ft converted to meters + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'JCWzsLQHmyfDHzQhi9_pU', + name: 'Dense Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, // Original 6ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'dense' + } + ] + } + }, + { + id: 'RsLZFtSi3oJH7uufQ5rc4', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'X2tAKuwUDc728RIPfhJUS', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 0 + } + ] + } + }, + { + id: 'GbEHhCMPmVom_IJK-xIn3', + name: 'Inbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, // Original 8ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'z4gZgzYoM7sQ7mzIV01PC', + name: 'Inbound Drive Lane 1', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'n9A8XDtjRSpgxElVhxoWB', + name: 'Inbound Drive Lane 2', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'O08G5Br9w6vwdomdhUmwk', + name: 'Outbound Drive Lane 1', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: '1w9jjehQwnvBfJeSVOd6M', + name: 'Outbound Drive Lane 2', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'ARosTXeWGXp17QyfZgSKB', + name: 'Outbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, // Original 8ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: '2p_cReSRF4748HV9Fyejr', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 180 + } + ] + } + }, + { + id: 'vL9qDNp5neZt32zlZ9ExG', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'RClRRZoof9_BYnqQm7mz-', + name: 'Normal Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, // Original 6ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + } + ] +}; diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index d6ffa19a..ef6a2bcf 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -85,6 +85,14 @@ export const streetLayersData = [ description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', id: 10, handlerFunction: createFunctions.create40ftRightOfWayManagedStreet + }, + { + name: '(Beta) Managed Street 80ft RoW / 56ft Roadway Width', + img: 'ui_assets/cards/street-preset-80-56.jpg', + icon: 'ui_assets/cards/icons/3dst24.png', + description: 'Premade Street 80ft Right of Way / 56ft Roadway Width', + id: 11, + handlerFunction: createFunctions.create80ftRightOfWayManagedStreet } ]; From 4d36fb8869f71471dfd6f64e15da551562fa3958 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 12:16:32 -0800 Subject: [PATCH 17/53] 94-foot stroad in Managed Street format Co-Authored-By: Ben Kovitz --- src/assets.js | 1 + src/components/street-generated-striping.js | 4 + src/components/street-segment.js | 11 +- .../AddLayerPanel/createLayerFunctions.js | 11 + .../AddLayerPanel/defaultStreets.js | 297 ++++++++++++++++++ .../components/AddLayerPanel/layersData.js | 8 + 6 files changed, 328 insertions(+), 4 deletions(-) diff --git a/src/assets.js b/src/assets.js index 5862e059..de555d11 100644 --- a/src/assets.js +++ b/src/assets.js @@ -147,6 +147,7 @@ function buildAssetHTML(assetUrl, categories) { + diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index bff0279f..0367b297 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -116,6 +116,10 @@ AFRAME.registerComponent('street-generated-striping', { stripingTextureId = 'striping-solid-dashed'; color = '#f7d117'; stripingWidth = 0.4; + } else if (stripingName === 'solid-dashed-yellow-mirror') { + stripingTextureId = 'striping-solid-dashed-mirror'; + color = '#f7d117'; + stripingWidth = 0.4; } return { stripingTextureId, repeatY, color, stripingWidth }; } diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 82b31e2c..0098c761 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -223,20 +223,23 @@ AFRAME.registerComponent('street-segment', { if (componentsToGenerate?.stencil?.length > 0) { componentsToGenerate.stencil.forEach((clone, index) => { if (clone?.stencils?.length > 0) { + // case where there are multiple stencils such as bus-only this.el.setAttribute(`street-generated-stencil__${index}`, { stencils: clone.stencils, length: this.data.length, spacing: clone.spacing, - direction: this.data.direction, - padding: clone.padding + direction: clone.direction ?? this.data.direction, + padding: clone.padding, + cycleOffset: clone.cycleOffset }); } else { this.el.setAttribute(`street-generated-stencil__${index}`, { model: clone.model, length: this.data.length, spacing: clone.spacing, - direction: this.data.direction, - count: clone.count + direction: clone.direction ?? this.data.direction, + count: clone.count, + cycleOffset: clone.cycleOffset }); } }); diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 6fb73f51..eef454e9 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -173,6 +173,17 @@ export function create80ftRightOfWayManagedStreet(position) { ); } +export function create94ftRightOfWayManagedStreet(position) { + console.log( + 'create94ftRightOfWayManagedStreet', + defaultStreetObjects.stroad94ftROW + ); + createManagedStreetFromStreetObject( + position, + defaultStreetObjects.stroad94ftROW + ); +} + export function create80ftRightOfWay(position) { createStreetmixStreet( position, diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js index ac175e78..d13486a7 100644 --- a/src/editor/components/components/AddLayerPanel/defaultStreets.js +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -741,3 +741,300 @@ export const stroad80ftROW = { } ] }; + +export const stroad94ftROW = { + id: 'a55d288c-215d-49a2-b67f-3efb9ec9ff41', + name: '94ft Right of Way 70ft Road Width', + width: 28.651, // Original 94ft converted to meters + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'JCWzsLQHmyfDHzQhi9_pU', + name: 'Dense Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, // Original 6ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'dense' + } + ] + } + }, + { + id: 'RsLZFtSi3oJH7uufQ5rc4', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'X2tAKuwUDc728RIPfhJUS', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 0 + } + ] + } + }, + { + id: 'GbEHhCMPmVom_IJK-xIn3', + name: 'Inbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, // Original 8ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'z4gZgzYoM7sQ7mzIV01PC', + name: 'Inbound Drive Lane 1', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'n9A8XDtjRSpgxElVhxoWB', + name: 'Inbound Drive Lane 2', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'zUl55HA-DUaJpyQEelUhW', + name: 'Center Turn Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'inbound', + generated: { + stencil: [ + { + model: 'left', + cycleOffset: 0.6, + spacing: 20, + direction: 'outbound' + }, + { + model: 'left', + cycleOffset: 0.4, + spacing: 20, + direction: 'inbound' + } + ], + striping: [ + { + striping: 'solid-dashed-yellow' + } + ] + } + }, + { + id: 'O08G5Br9w6vwdomdhUmwk', + name: 'Outbound Drive Lane 1', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ], + striping: [ + { + striping: 'solid-dashed-yellow-mirror' + } + ] + } + }, + { + id: '1w9jjehQwnvBfJeSVOd6M', + name: 'Outbound Drive Lane 2', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'ARosTXeWGXp17QyfZgSKB', + name: 'Outbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, // Original 8ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: '2p_cReSRF4748HV9Fyejr', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 180 + } + ] + } + }, + { + id: 'vL9qDNp5neZt32zlZ9ExG', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'RClRRZoof9_BYnqQm7mz-', + name: 'Normal Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 1.829, // Original 6ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + } + ] +}; diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index ef6a2bcf..28868ea3 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -93,6 +93,14 @@ export const streetLayersData = [ description: 'Premade Street 80ft Right of Way / 56ft Roadway Width', id: 11, handlerFunction: createFunctions.create80ftRightOfWayManagedStreet + }, + { + name: '(Beta) Managed Street 94ft RoW 70ft Roadway Width', + img: 'ui_assets/cards/street-preset-94-70.jpg', + icon: 'ui_assets/cards/icons/3dst24.png', + description: 'Premade Street 94ft Right of Way / 70ft Roadway Width', + id: 12, + handlerFunction: createFunctions.create94ftRightOfWayManagedStreet } ]; From c5df9223f23cb9dfcaf9beaffebc3f0c1db8f11a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 13:42:32 -0800 Subject: [PATCH 18/53] keep center lane striping in center lane --- .../components/components/AddLayerPanel/defaultStreets.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js index d13486a7..c4dadb47 100644 --- a/src/editor/components/components/AddLayerPanel/defaultStreets.js +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -883,7 +883,6 @@ export const stroad94ftROW = { color: '#ffffff', level: 0, width: 3.048, // Original 10ft - direction: 'inbound', generated: { stencil: [ { @@ -902,6 +901,10 @@ export const stroad94ftROW = { striping: [ { striping: 'solid-dashed-yellow' + }, + { + striping: 'solid-dashed-yellow-mirror', + side: 'right' } ] } @@ -927,7 +930,7 @@ export const stroad94ftROW = { ], striping: [ { - striping: 'solid-dashed-yellow-mirror' + striping: 'none' } ] } From c581d96624f09fe12b66f7b68c6e757b7542fe45 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 14:22:48 -0800 Subject: [PATCH 19/53] v1 of basic streetplan mapping --- src/components/managed-street.js | 91 +++++++++++++++++++++++++++++++- 1 file changed, 90 insertions(+), 1 deletion(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 73b75cda..0470e4dc 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -5,6 +5,67 @@ const { segmentVariants } = require('../segments-variants.js'); const streetmixUtils = require('../tested/streetmix-utils'); const streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested'); +// STREETPLAN HELPER FUNCTIONS +// Material mapping from Streetplan to 3DStreet surfaces +const STREETPLAN_MATERIAL_MAPPING = { + 'asphalt black': 'asphalt', + 'asphalt blue': 'asphalt', + 'asphalt red 1': 'asphalt', + 'asphalt red 2': 'asphalt', + 'asphalt green': 'asphalt', + 'asphalt old': 'asphalt', + 'standard concrete': 'concrete', + grass: 'grass', + 'grass dead': 'grass', + 'pavers tan': 'sidewalk', + 'pavers brown': 'sidewalk', + 'pavers mixed': 'sidewalk', + 'pavers red': 'sidewalk', + 'tint conc. or dirt': 'gravel', + dirt: 'gravel', + gravel: 'gravel', + stonetan: 'sidewalk', + 'sidewalk 2': 'sidewalk', + 'cobble stone': 'sidewalk', + 'solid black': 'solid', + 'painted intersection': 'asphalt', + 'grass with edging': 'grass', + xeriscape: 'grass', + 'grassslopemedian 12ft': 'grass', + 'grassslopemedian 24ft': 'grass', + 'grassslope 12ft-left': 'grass', + 'grassslope 12ft-right': 'grass', + 'grassslope 24ft-left': 'grass', + 'grassslope 24ft-right': 'grass', + sand: 'sand' +}; + +const STREETPLAN_OBJECT_MAPPING = { + 'Japanese Zelkova': 'tree3', + 'TallPlantBox (12ft)': 'dividers-bush' + // Add more mappings as needed +}; + +// Helper function to parse O-Tags string into array +function parseOTags(tags) { + if (!tags || tags === '-') return []; + return tags.split('", "').map((t) => t.replace(/"/g, '').trim()); +} + +// Helper function to create clone configuration +function createCloneConfig(name, tags) { + if (!name || name === '-') return null; + + const model = STREETPLAN_OBJECT_MAPPING[name]; + if (!model) return null; + + return { + mode: 'fixed', // default to fixed mode + model: model, + spacing: 15 // default spacing + }; +} + AFRAME.registerComponent('managed-street', { schema: { width: { @@ -304,6 +365,12 @@ AFRAME.registerComponent('managed-street', { const segments = boulevard.segments; for (const segmentKey in segments) { const segment = segments[segmentKey]; + + // Skip Buildings and Setback segments + if (segment.Type === 'Buildings' || segment.Type === 'Setback') { + continue; + } + const segmentWidth = parseFloat(segment.width) * 0.3048; // Convert feet to meters streetObject.width += segmentWidth; @@ -345,6 +412,27 @@ AFRAME.registerComponent('managed-street', { segmentDirection = 'outbound'; } + // Map the material using the STREETPLAN_MATERIAL_MAPPING, fallback to 'asphalt' if not found + const material = segment.Material?.toLowerCase() || ''; + const mappedSurface = + STREETPLAN_MATERIAL_MAPPING[material] || 'asphalt'; + + // Map the O-Tags to clone configurations + const generated = {}; + const clones = []; + // Process O1, O2, O3 configurations + ['O1', 'O2', 'O3'].forEach((prefix) => { + const name = segment[`${prefix}-Name`]; + const tags = parseOTags(segment[`${prefix}-Tags`]); + const cloneConfig = createCloneConfig(name, tags); + if (cloneConfig) { + clones.push(cloneConfig); + } + }); + if (clones.length > 0) { + generated.clones = clones; + } + streetObject.segments.push({ type: segmentType, width: segmentWidth, @@ -352,7 +440,8 @@ AFRAME.registerComponent('managed-street', { level: parseFloat(segment.MaterialH) || 0, direction: segmentDirection, color: segmentColor, - surface: segment.Material?.toLowerCase() || 'asphalt' + surface: mappedSurface, + generated: clones.length > 0 ? generated : undefined }); } From 29c9932c71e81aade09fc0ac110fc223c5a43f2a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 14:35:33 -0800 Subject: [PATCH 20/53] streetplan object mapping --- src/components/managed-street.js | 116 ++++++++++++++++++++++++++++++- 1 file changed, 114 insertions(+), 2 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 0470e4dc..f9b1a606 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -41,9 +41,121 @@ const STREETPLAN_MATERIAL_MAPPING = { }; const STREETPLAN_OBJECT_MAPPING = { + 'Away, Left Park, Head In': '', + 'Barrier 1-ft': '', + 'Barrier 2-ft': '', + 'Bike Food Cart': '', + 'BikeRack Bollard': '', + 'Bikelane ShareCar': '', + 'Blank PedRefuge (8ft)': '', + 'Blue Car': '', + 'Blue Mailbox': '', + 'Bollard Plastic Yellow': '', + 'Boxwood planter 2ft': '', + 'Boxwood planter 3ft': '', + 'Boxwood planter 5ft': '', + 'Bur Oak': 'tree3', + 'Cactus Median (10ft)': '', + 'Cactus Median (12ft)': '', + 'Cactus Median (4ft)': '', + 'Cactus Median (6ft)': '', + 'Cactus Median (8ft)': '', + 'DesertWillow Texas': 'tree3', + 'Empty place holder': '', + 'English oak': 'tree3', + 'FleaMarket Stuff': '', + 'Flower Median (10ft)': '', + 'Flower Median (12ft)': '', + 'Flower Median (4ft)': '', + 'Flower Median (6ft)': '', + 'Flower Median (8ft)': '', + 'Flower Pot 4ft': '', + 'FloweringPear 18ft': '', + 'Flowers PedRefuge (8ft)': '', + Goldenraintree: 'tree3', + 'GrassMound (10ft)': '', + 'GrassMound (12ft)': '', + 'GrassMound (4ft)': '', + 'GrassMound (6ft)': '', + 'GrassMound (8ft)': '', + 'Grassy Median (10ft)': '', + 'Grassy Median (12ft)': '', + 'Grassy Median (4ft)': '', + 'Grassy Median (6ft)': '', + 'Grassy Median (8ft)': '', + 'Green Car': '', + 'Historic Light': '', + Honeylocust: '', 'Japanese Zelkova': 'tree3', - 'TallPlantBox (12ft)': 'dividers-bush' - // Add more mappings as needed + 'Japanese lilac': 'tree3', + 'Jerusalem Thorn': 'tree3', + 'Kentucky Coffeetree': 'tree3', + 'Large Food Cart': '', + 'Large Oak': '', + 'Light rail poles': '', + 'Moto highway rider': '', + 'Mountable Barrier 1-ft': '', + 'NYC Bike Rack': '', + 'Orange Barrel': '', + 'Palm Tree': 'palm-tree', + 'PalmTree 20ft': 'palm-tree', + 'PalmTree 28ft': 'palm-tree', + 'Pine Tree': 'tree3', + 'Pink flower 16ft': '', + 'Planter flowers': '', + 'Planter with bench': '', + 'Power Tower 30ft': '', + 'Purpendicular Right side, Blue': '', + 'Purpendicular Right side, Red': '', + 'Purpleleaf plum': '', + 'Red berries 14ft': '', + 'Rock Median (10ft)': '', + 'Rock Median (12ft)': '', + 'Rock Median (4ft)': '', + 'Rock Median (6ft)': '', + 'Rock Median (8ft)': '', + 'Semi Truck': '', + Shelter: '', + 'Shelter Roundroof': '', + 'Sign directory': '', + 'Small Tree': 'tree3', + 'SmartCar 5ft': '', + 'SoundWall (12ft)': '', + 'SoundWall (8ft)': '', + 'SoundWall Plants (12ft)': '', + 'SoundWall Plants (8ft)': '', + 'Street light': '', + 'Streetlight solar': '', + 'Streetlight solar banners 1': '', + 'Streetlight solar banners 2': '', + TallGrass: '', + 'TallPlantBox (10ft)': '', + 'TallPlantBox (12ft)': 'dividers-bush', + 'TallPlantBox (4ft)': '', + 'TallPlantBox (6ft)': '', + 'TallPlantBox (8ft)': '', + 'TallPlantBox PedRef (10ft)': '', + 'TallPlantBox PedRef (12ft)': '', + 'TallPlantBox PedRef (6ft)': '', + 'TallPlantBox PedRef (8ft)': '', + 'Telephone pole': '', + 'Tent BlueWhite': '', + 'Tent Veggie': '', + 'Toward, Right Park, Head In': '', + 'Tropical Median (4ft)': '', + 'Weeds Median (4ft)': '', + 'Weeds Median (6ft)': '', + 'Weeds Median (8ft)': '', + 'White Sedan': '', + 'White Truck': '', + 'White coup': '', + 'Yellow Sedan': '', + 'historic no banner': '', + 'historic with banners': '', + 'historic with flowers 1': '', + 'historic with flowers 2': '', + 'random trashcan': '', + trashcan: '' }; // Helper function to parse O-Tags string into array From 3f7b24a7b078fc62b355005f84993ed712c840c7 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 31 Dec 2024 21:52:10 -0800 Subject: [PATCH 21/53] add 150 row --- .../AddLayerPanel/createLayerFunctions.js | 11 + .../AddLayerPanel/defaultStreets.js | 428 ++++++++++++++++++ .../components/AddLayerPanel/layersData.js | 10 +- 3 files changed, 448 insertions(+), 1 deletion(-) diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index eef454e9..047f71e8 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -184,6 +184,17 @@ export function create94ftRightOfWayManagedStreet(position) { ); } +export function create150ftRightOfWayManagedStreet(position) { + console.log( + 'create150ftRightOfWayManagedStreet', + defaultStreetObjects.stroad150ftROW + ); + createManagedStreetFromStreetObject( + position, + defaultStreetObjects.stroad150ftROW + ); +} + export function create80ftRightOfWay(position) { createStreetmixStreet( position, diff --git a/src/editor/components/components/AddLayerPanel/defaultStreets.js b/src/editor/components/components/AddLayerPanel/defaultStreets.js index c4dadb47..76b8a882 100644 --- a/src/editor/components/components/AddLayerPanel/defaultStreets.js +++ b/src/editor/components/components/AddLayerPanel/defaultStreets.js @@ -1041,3 +1041,431 @@ export const stroad94ftROW = { } ] }; + +export const stroad150ftROW = { + id: 'f8eeb25c-f68c-4f0b-9435-3f87e6be705a', + name: '150ft Right of Way 124ft Road Width', + width: 45.72, // Original 150ft converted to meters + length: 100, + justifyWidth: 'center', + justifyLength: 'start', + segments: [ + { + id: 'JCWzsLQHmyfDHzQhi9_pU', + name: 'Dense Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 2.134, // Original 7ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'dense' + } + ] + } + }, + { + id: 'RsLZFtSi3oJH7uufQ5rc4', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'X2tAKuwUDc728RIPfhJUS', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 0 + } + ] + } + }, + { + id: 'GbEHhCMPmVom_IJK-xIn3', + name: 'Inbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, // Original 8ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: 'vLBLQS2VraoTL2sJRmD4J', + name: 'Inbound Left Turn Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, suv-rig', + spacing: 20, + count: 2 + } + ], + stencil: [ + { + model: 'turn-lane-left', + cycleOffset: 1, + spacing: 20 + } + ] + } + }, + { + id: 'z4gZgzYoM7sQ7mzIV01PC', + name: 'Inbound Drive Lane 1', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: '3a42u-6x8OsoGsI7bjb4z', + name: 'Inbound Truck Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'box-truck-rig, trailer-truck-rig', + spacing: 15, + count: 2 + } + ] + } + }, + { + id: 'n9A8XDtjRSpgxElVhxoWB', + name: 'Inbound Drive Lane 2', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'E1UvC71Nkre2H2-hg2gMd', + name: 'Inbound Right Turn Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'inbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, suv-rig', + spacing: 20, + count: 2 + } + ], + stencil: [ + { + model: 'turn-lane-right', + cycleOffset: 1, + spacing: 20 + } + ] + } + }, + { + id: 'WisaQ2Pfc5K51O8k_Mrnb', + name: 'Planted Median', + type: 'divider', + surface: 'planting-strip', + color: '#338833', + level: 1, + width: 0.61, // Original 2ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'flowers1', + spacing: 3 + } + ] + } + }, + { + id: 'qvQftgSPmiA7afQles5EK', + name: 'Outbound Left Turn Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, suv-rig', + spacing: 20, + count: 2 + } + ], + stencil: [ + { + model: 'turn-lane-left', + cycleOffset: 1, + spacing: 20 + } + ] + } + }, + { + id: 'O08G5Br9w6vwdomdhUmwk', + name: 'Outbound Drive Lane 1', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: '1w9jjehQwnvBfJeSVOd6M', + name: 'Outbound Truck Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'box-truck-rig, trailer-truck-rig', + spacing: 15, + count: 2 + } + ] + } + }, + { + id: 'RnfgVJLk2oJv7QTW_s3WR', + name: 'Outbound Drive Lane 2', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.353, // Original 11ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: 7.3, + count: 4 + } + ] + } + }, + { + id: 'va_9kr_Dtr9q8ddlzAl02', + name: 'Outbound Right Turn Lane', + type: 'drive-lane', + surface: 'asphalt', + color: '#ffffff', + level: 0, + width: 3.048, // Original 10ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, suv-rig', + spacing: 20, + count: 2 + } + ], + stencil: [ + { + model: 'turn-lane-right', + cycleOffset: 1, + spacing: 20 + } + ] + } + }, + { + id: 'ARosTXeWGXp17QyfZgSKB', + name: 'Outbound Parking', + type: 'parking-lane', + surface: 'concrete', + color: '#dddddd', + level: 0, + width: 2.438, // Original 8ft + direction: 'outbound', + generated: { + clones: [ + { + mode: 'random', + modelsArray: 'sedan-rig, self-driving-waymo-car, suv-rig', + spacing: 6, + count: 6 + } + ], + stencil: [ + { + model: 'parking-t', + cycleOffset: 1, + spacing: 6 + } + ] + } + }, + { + id: '2p_cReSRF4748HV9Fyejr', + name: 'Modern Street Lamp', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'lamp-modern', + spacing: 30, + facing: 180 + } + ] + } + }, + { + id: 'vL9qDNp5neZt32zlZ9ExG', + name: 'Tree Planting Strip', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 0.914, // Original 3ft + direction: 'none', + generated: { + clones: [ + { + mode: 'fixed', + model: 'tree3', + spacing: 15 + } + ] + } + }, + { + id: 'RClRRZoof9_BYnqQm7mz-', + name: 'Normal Sidewalk', + type: 'sidewalk', + surface: 'sidewalk', + color: '#ffffff', + level: 1, + width: 2.134, // Original 7ft + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + } + ] +}; diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 28868ea3..bcd3e924 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -95,12 +95,20 @@ export const streetLayersData = [ handlerFunction: createFunctions.create80ftRightOfWayManagedStreet }, { - name: '(Beta) Managed Street 94ft RoW 70ft Roadway Width', + name: '(Beta) Managed Street 94ft RoW / 70ft Roadway Width', img: 'ui_assets/cards/street-preset-94-70.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 94ft Right of Way / 70ft Roadway Width', id: 12, handlerFunction: createFunctions.create94ftRightOfWayManagedStreet + }, + { + name: '(Beta) Managed Street 150ft RoW / 124ft Roadway Width', + img: 'ui_assets/cards/street-preset-150-124.jpg', + icon: 'ui_assets/cards/icons/3dst24.png', + description: 'Premade Street 150ft Right of Way / 124ft Roadway Width', + id: 6, + handlerFunction: createFunctions.create150ftRightOfWayManagedStreet } ]; From 14272987720d0861f46532b3ee12398f43300bfe Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 1 Jan 2025 17:09:13 -0800 Subject: [PATCH 22/53] mvp polygon offset --- index.html | 2 +- src/components/managed-street.js | 11 +++++++++++ src/components/street-generated-stencil.js | 10 ++++++++++ src/components/street-generated-striping.js | 10 ++++++++++ 4 files changed, 32 insertions(+), 1 deletion(-) diff --git a/index.html b/index.html index 2be1e2ac..a03407bc 100644 --- a/index.html +++ b/index.html @@ -65,7 +65,7 @@ --> diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 32bdcf0f..b7e974d7 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -237,6 +237,17 @@ AFRAME.registerComponent('managed-street', { 'position', `${xPosition} -1 ${zPosition}` ); + + this.justifiedDirtBox.addEventListener('loaded', () => { + // Fix z fighting issue + const mesh = this.justifiedDirtBox.getObject3D('mesh'); + if (mesh) { + const material = mesh.material; + material.polygonOffset = true; + material.polygonOffsetFactor = 4; + material.polygonOffsetUnits = 4; + } + }); }, parseStreetObject: function (streetObject) { // reset and delete all existing entities diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index b78e3396..b8182a25 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -169,6 +169,16 @@ AFRAME.registerComponent('street-generated-stencil', { this.el.appendChild(clone); this.createdEntities.push(clone); + // Fix z fighting issue + clone.addEventListener('loaded', () => { + const mesh = clone.getObject3D('mesh'); + if (mesh) { + const material = mesh.material; + material.polygonOffset = true; + material.polygonOffsetFactor = -2; + material.polygonOffsetUnits = -2; + } + }); }); } } diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 8714eb72..2badc6c1 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -77,6 +77,16 @@ AFRAME.registerComponent('street-generated-striping', { ); this.el.appendChild(clone); this.createdEntities.push(clone); + // Fix z fighting issue + clone.addEventListener('loaded', () => { + const mesh = clone.getObject3D('mesh'); + if (mesh) { + const material = mesh.material; + material.polygonOffset = true; + material.polygonOffsetFactor = -2; + material.polygonOffsetUnits = -2; + } + }); }, calculateStripingMaterial: function (stripingName, length) { // calculate the repeatCount for the material From c80099ec36c83ef2bc07ed6cd39193b48f633767 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 1 Jan 2025 17:25:06 -0800 Subject: [PATCH 23/53] fix title wrapping --- .../components/AddLayerPanel/AddLayerPanel.module.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/editor/components/components/AddLayerPanel/AddLayerPanel.module.scss b/src/editor/components/components/AddLayerPanel/AddLayerPanel.module.scss index af61b965..092c558a 100644 --- a/src/editor/components/components/AddLayerPanel/AddLayerPanel.module.scss +++ b/src/editor/components/components/AddLayerPanel/AddLayerPanel.module.scss @@ -74,6 +74,10 @@ .description { color: rgba(182, 182, 182, 1); + max-width: 182px; // Same as image width + word-wrap: break-word; + overflow-wrap: break-word; + white-space: normal; } } From 76a0c0f56c5ea1397f1da3745cf893aed221e2dd Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 1 Jan 2025 17:34:52 -0800 Subject: [PATCH 24/53] fix street card ordering --- .../components/AddLayerPanel/layersData.js | 49 +++++++++---------- 1 file changed, 24 insertions(+), 25 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index bcd3e924..0a51157e 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -1,13 +1,22 @@ import * as createFunctions from './createLayerFunctions'; export const streetLayersData = [ + { + name: 'Create Intersection', + img: '', + requiresPro: true, + icon: 'ui_assets/cards/icons/3dst24.png', + description: 'Create 90º intersection entity.', + id: 1, + handlerFunction: createFunctions.createIntersection + }, { name: 'Street from Streetmix URL', img: 'ui_assets/cards/streetmix.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Create an additional Streetmix street in your 3DStreet scene without replacing any existing streets.', - id: 1, + id: 2, handlerFunction: createFunctions.createStreetmixStreet }, { @@ -15,7 +24,7 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-40-24.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', - id: 2, + id: 3, handlerFunction: createFunctions.create40ftRightOfWay }, { @@ -23,7 +32,7 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-60-36.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', - id: 3, + id: 4, handlerFunction: createFunctions.create60ftRightOfWay }, { @@ -31,7 +40,7 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-80-56.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 80ft Right of Way / 56ft Roadway Width', - id: 4, + id: 5, handlerFunction: createFunctions.create80ftRightOfWay }, { @@ -39,7 +48,7 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-94-70.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 94ft Right of Way / 70ft Roadway Width', - id: 5, + id: 6, handlerFunction: createFunctions.create94ftRightOfWay }, { @@ -47,44 +56,34 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-150-124.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 150ft Right of Way / 124ft Roadway Width', - id: 6, - handlerFunction: createFunctions.create150ftRightOfWay - }, - { - name: 'Create intersection', - img: '', - requiresPro: true, - icon: '', - description: - 'Create intersection entity. Parameters of intersection component could be changed in properties panel.', id: 7, - handlerFunction: createFunctions.createIntersection + handlerFunction: createFunctions.create150ftRightOfWay }, { name: '(Beta) Managed Street from Streetmix URL', img: '', requiresPro: true, - icon: '', + icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Create a new street from Streetmix URL using the Managed Street component.', id: 8, handlerFunction: createFunctions.createManagedStreetFromStreetmixURLPrompt }, { - name: '(Beta) Managed Street 60ft RoW / 36ft Roadway Width', - img: 'ui_assets/cards/street-preset-60-36.jpg', + name: '(Beta) Managed Street 40ft RoW / 24ft Roadway Width', + img: 'ui_assets/cards/street-preset-40-24.jpg', icon: 'ui_assets/cards/icons/3dst24.png', - description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', + description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', id: 9, - handlerFunction: createFunctions.create60ftRightOfWayManagedStreet + handlerFunction: createFunctions.create40ftRightOfWayManagedStreet }, { - name: '(Beta) Managed Street 40ft RoW / 24ft Roadway Width', - img: 'ui_assets/cards/street-preset-40-24.jpg', + name: '(Beta) Managed Street 60ft RoW / 36ft Roadway Width', + img: 'ui_assets/cards/street-preset-60-36.jpg', icon: 'ui_assets/cards/icons/3dst24.png', - description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', + description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', id: 10, - handlerFunction: createFunctions.create40ftRightOfWayManagedStreet + handlerFunction: createFunctions.create60ftRightOfWayManagedStreet }, { name: '(Beta) Managed Street 80ft RoW / 56ft Roadway Width', From 8b3558b33ead8661a26880a77e3043d4314b6882 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 3 Jan 2025 11:14:57 -0800 Subject: [PATCH 25/53] fix duplicate id --- src/editor/components/components/AddLayerPanel/layersData.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 0a51157e..7aa6f3c4 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -106,7 +106,7 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-150-124.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 150ft Right of Way / 124ft Roadway Width', - id: 6, + id: 13, handlerFunction: createFunctions.create150ftRightOfWayManagedStreet } ]; From 0beff71f15ca1c2a300b4c4b08abf0f4c5ba7f1b Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 5 Jan 2025 21:42:08 -0800 Subject: [PATCH 26/53] barely working alignment component --- src/components/managed-street.js | 59 +--------- src/components/street-align.js | 186 +++++++++++++++++++++++++++++++ src/components/street-segment.js | 10 +- src/index.js | 1 + 4 files changed, 201 insertions(+), 55 deletions(-) create mode 100644 src/components/street-align.js diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 5eec3f9e..a6269ee2 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -36,15 +36,9 @@ AFRAME.registerComponent('managed-street', { type: 'boolean', default: true }, - justifyWidth: { - default: 'center', - type: 'string', - oneOf: ['center', 'left', 'right'] - }, - justifyLength: { - default: 'middle', - type: 'string', - oneOf: ['middle', 'start', 'end'] + enableAlignment: { + type: 'boolean', + default: true } }, init: function () { @@ -52,6 +46,9 @@ AFRAME.registerComponent('managed-street', { this.pendingEntities = []; // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); + if (this.data.enableAlignment && !this.el.hasAttribute('street-align')) { + this.el.setAttribute('street-align', ''); + } }, /** * Inserts a new street segment at the specified index @@ -123,9 +120,6 @@ AFRAME.registerComponent('managed-street', { }, 0); this.el.setAttribute('managed-street', 'width', totalWidth); - // Apply justification to reposition all segments - this.applyJustification(); - // Update the dirt box this.createOrUpdateJustifiedDirtBox(); @@ -157,7 +151,6 @@ AFRAME.registerComponent('managed-street', { // If segments were removed, trigger reflow if (needsReflow) { this.refreshManagedEntities(); - this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); } }); @@ -183,7 +176,6 @@ AFRAME.registerComponent('managed-street', { dataDiffKeys.includes('justifyLength')) ) { this.refreshManagedEntities(); - this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); } @@ -227,43 +219,6 @@ AFRAME.registerComponent('managed-street', { segmentEl.setAttribute('street-segment', 'length', streetLength); }); }, - applyJustification: function () { - const data = this.data; - const segmentEls = this.managedEntities; - const streetWidth = data.width; - const streetLength = data.length; - - // set starting xPosition for width justification - let xPosition = 0; // default for left justified - if (data.justifyWidth === 'center') { - xPosition = -streetWidth / 2; - } - if (data.justifyWidth === 'right') { - xPosition = -streetWidth; - } - // set z value for length justification - let zPosition = 0; // default for middle justified - if (data.justifyLength === 'start') { - zPosition = -streetLength / 2; - } - if (data.justifyLength === 'end') { - zPosition = streetLength / 2; - } - - segmentEls.forEach((segmentEl) => { - if (!segmentEl.getAttribute('street-segment')) { - return; - } - const segmentWidth = segmentEl.getAttribute('street-segment').width; - const yPosition = segmentEl.getAttribute('position').y; - xPosition += segmentWidth / 2; - segmentEl.setAttribute( - 'position', - `${xPosition} ${yPosition} ${zPosition}` - ); - xPosition += segmentWidth / 2; - }); - }, refreshManagedEntities: function () { // create a list again of the managed entities this.managedEntities = Array.from( @@ -372,7 +327,6 @@ AFRAME.registerComponent('managed-street', { segmentEl.components[ 'street-segment' ].generateComponentsFromSegmentObject(segment); - this.applyJustification(); }); } }, @@ -498,7 +452,6 @@ AFRAME.registerComponent('managed-street', { // When all entities are loaded, do something with them this.allLoadedPromise.then(() => { this.refreshManagedEntities(); - this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); AFRAME.INSPECTOR.selectEntity(this.el); }); diff --git a/src/components/street-align.js b/src/components/street-align.js new file mode 100644 index 00000000..c57962cc --- /dev/null +++ b/src/components/street-align.js @@ -0,0 +1,186 @@ +/* global AFRAME */ + +AFRAME.registerComponent('street-align', { + schema: { + width: { + default: 'center', + type: 'string', + oneOf: ['center', 'left', 'right'] + }, + length: { + default: 'start', + type: 'string', + oneOf: ['middle', 'start', 'end'] + } + }, + + init: function () { + this.monitoredSegments = []; + + this.boundWidthChangedHandler = this.onSegmentWidthChanged.bind(this); + + // Initial setup + this.refreshMonitoredSegments(); // This now handles initial segment listeners + this.setupMutationObserver(); + this.realignStreet(); + + // for when loading from saved scene, add set timeout to refresh the segments and realign + setTimeout(() => { + this.refreshMonitoredSegments(); + this.realignStreet(); + }, 2000); + }, + + setupSegmentListeners: function () { + // Set up listeners for all existing segments + const segments = this.el.querySelectorAll('[street-segment]'); + segments.forEach((segment) => this.addSegmentListener(segment)); + }, + + addSegmentListener: function (segment) { + // Listen for width changes + segment.addEventListener( + 'segment-width-changed', + this.boundWidthChangedHandler + ); + }, + + removeSegmentListener: function (segment) { + // Remove listeners + segment.removeEventListener( + 'segment-width-changed', + this.boundWidthChangedHandler + ); + const index = this.monitoredSegments.indexOf(segment); + if (index > -1) { + this.monitoredSegments.splice(index, 1); + } + }, + + onSegmentWidthChanged: function (event) { + console.log('segment width changed handler called', event); + this.refreshMonitoredSegments(); + this.realignStreet(); + }, + + refreshMonitoredSegments: function () { + // Clear existing listeners + this.monitoredSegments.forEach((segment) => { + this.removeSegmentListener(segment); + }); + + // Reset the list + this.monitoredSegments = []; + + // Reset and repopulate the list + this.monitoredSegments = Array.from( + this.el.querySelectorAll('[street-segment]') + ); + + // Add new listeners + const segments = this.el.querySelectorAll('[street-segment]'); + segments.forEach((segment) => { + this.addSegmentListener(segment); + }); + console.log('monitored segments', this.monitoredSegments); + }, + + update: function (oldData) { + const data = this.data; + const diff = AFRAME.utils.diff(oldData, data); + + // Only realign if width or length alignment changed + if (diff.width !== undefined || diff.length !== undefined) { + // this.alignStreetSegments(); + this.realignStreet(); + } + }, + + setupMutationObserver: function () { + if (this.observer) { + this.observer.disconnect(); + } + + this.observer = new MutationObserver((mutations) => { + let needsReflow = false; + + mutations.forEach((mutation) => { + if (mutation.type === 'childList') { + // Instead of handling segments individually, refresh the entire list + if ( + Array.from(mutation.addedNodes).some( + (node) => node.hasAttribute && node.hasAttribute('street-segment') + ) || + Array.from(mutation.removedNodes).some( + (node) => node.hasAttribute && node.hasAttribute('street-segment') + ) + ) { + needsReflow = true; + } + } + }); + + if (needsReflow) { + this.refreshMonitoredSegments(); + this.realignStreet(); + } + }); + + this.observer.observe(this.el, { + childList: true, + subtree: false + }); + }, + + realignStreet: function () { + const data = this.data; + if (this.monitoredSegments.length === 0) return; + + // Calculate total width + const totalWidth = this.monitoredSegments.reduce((sum, segment) => { + return sum + (segment.getAttribute('street-segment')?.width || 0); + }, 0); + console.log('total width', totalWidth); + + // Get street length from managed-street component + const streetLength = this.el.getAttribute('managed-street')?.length || 0; + + // Calculate starting positions + let xPosition = 0; + if (data.width === 'center') { + xPosition = -totalWidth / 2; + } else if (data.width === 'right') { + xPosition = -totalWidth; + } + + let zPosition = 0; + if (data.length === 'start') { + zPosition = -streetLength / 2; + } else if (data.length === 'end') { + zPosition = streetLength / 2; + } + + // Position segments + this.monitoredSegments.forEach((segment) => { + const width = segment.getAttribute('street-segment')?.width; + const currentPos = segment.getAttribute('position'); + + xPosition += width / 2; + + segment.setAttribute('position', { + x: xPosition, + y: currentPos.y, + z: zPosition + }); + + xPosition += width / 2; + }); + }, + + remove: function () { + if (this.observer) { + this.observer.disconnect(); + this.observer = null; + } + } +}); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 0098c761..ded6eac0 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -326,8 +326,14 @@ AFRAME.registerComponent('street-segment', { this.generateMesh(data); // if width was changed, trigger re-justification of all street-segments by the managed-street if (changedProps.includes('width')) { - this.el.parentNode.components['managed-street'].refreshManagedEntities(); - this.el.parentNode.components['managed-street'].applyJustification(); + console.log('segment width changed'); + this.el.emit('segment-width-changed', { + oldWidth: oldData.width, + newWidth: data.width + }); + + // this.el.parentNode.components['managed-street'].refreshManagedEntities(); + // this.el.parentNode.components['managed-street'].applyJustification(); } }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units diff --git a/src/index.js b/src/index.js index a217dd5b..a3f17487 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ require('./components/street-generated-striping.js'); require('./components/street-generated-pedestrians.js'); require('./components/street-generated-rail.js'); require('./components/street-generated-clones.js'); +require('./components/street-align.js'); require('./editor/index.js'); var firebase = require('./editor/services/firebase.js'); From 77525eca0759b961016ea60ce0135dc124f02c3f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 09:47:39 -0800 Subject: [PATCH 27/53] better justification of dirtbox --- src/components/managed-street.js | 29 ++++++++++++++++------------- src/components/street-align.js | 2 +- src/components/street-segment.js | 6 ++++-- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index a6269ee2..c7efc543 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -44,6 +44,7 @@ AFRAME.registerComponent('managed-street', { init: function () { this.managedEntities = []; this.pendingEntities = []; + this.actualWidth = 0; // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); if (this.data.enableAlignment && !this.el.hasAttribute('street-align')) { @@ -170,14 +171,6 @@ AFRAME.registerComponent('managed-street', { } const dataDiffKeys = Object.keys(dataDiff); - if ( - dataDiffKeys.length === 1 && - (dataDiffKeys.includes('justifyWidth') || - dataDiffKeys.includes('justifyLength')) - ) { - this.refreshManagedEntities(); - this.createOrUpdateJustifiedDirtBox(); - } if (dataDiffKeys.includes('width')) { this.createOrUpdateJustifiedDirtBox(); @@ -225,8 +218,14 @@ AFRAME.registerComponent('managed-street', { this.el.querySelectorAll('[street-segment]') ); this.setupMutationObserver(); + // calculate actual width + this.actualWidth = this.managedEntities.reduce((sum, segment) => { + return sum + (segment.getAttribute('street-segment')?.width || 0); + }, 0); + console.log('actual width', this.actualWidth); }, createOrUpdateJustifiedDirtBox: function () { + console.log('createOrUpdateJustifiedDirtBox'); const data = this.data; const streetWidth = data.width; if (!streetWidth) { @@ -249,24 +248,28 @@ AFRAME.registerComponent('managed-street', { dirtBox.setAttribute('data-ignore-raycaster', ''); } this.justifiedDirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 - this.justifiedDirtBox.setAttribute('width', streetWidth); + this.justifiedDirtBox.setAttribute('width', this.actualWidth); this.justifiedDirtBox.setAttribute('depth', streetLength - 0.2); // depth is length - 0.1 on each side + // instead of data.justifyWidth, use [street-align] component to get width justification + const alignWidth = this.el.getAttribute('street-align')?.width; + const alignLength = this.el.getAttribute('street-align')?.length; + // set starting xPosition for width justification let xPosition = 0; // default for center justified - if (data.justifyWidth === 'left') { + if (alignWidth === 'left') { xPosition = streetWidth / 2; } - if (data.justifyWidth === 'right') { + if (alignWidth === 'right') { xPosition = -streetWidth / 2; } // set z value for length justification let zPosition = 0; // default for middle justified - if (data.justifyLength === 'start') { + if (alignLength === 'start') { zPosition = -streetLength / 2; } - if (data.justifyLength === 'end') { + if (alignLength === 'end') { zPosition = streetLength / 2; } diff --git a/src/components/street-align.js b/src/components/street-align.js index c57962cc..d35a2712 100644 --- a/src/components/street-align.js +++ b/src/components/street-align.js @@ -24,7 +24,7 @@ AFRAME.registerComponent('street-align', { this.setupMutationObserver(); this.realignStreet(); - // for when loading from saved scene, add set timeout to refresh the segments and realign + // TODO: This is bad -- for when loading from saved scene, add set timeout to refresh the segments and realign setTimeout(() => { this.refreshMonitoredSegments(); this.realignStreet(); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index ded6eac0..2f449751 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -332,8 +332,10 @@ AFRAME.registerComponent('street-segment', { newWidth: data.width }); - // this.el.parentNode.components['managed-street'].refreshManagedEntities(); - // this.el.parentNode.components['managed-street'].applyJustification(); + this.el.parentNode.components['managed-street'].refreshManagedEntities(); + this.el.parentNode.components[ + 'managed-street' + ].createOrUpdateJustifiedDirtBox(); } }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units From 4a0607eaa923147fe7e3c547b97950c1d916bb81 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 11:30:00 -0800 Subject: [PATCH 28/53] simplify alignment event handling use central event dispatcher in managed-street instead of mutation observer in each component --- src/components/managed-street.js | 69 +++++++++++++++++++---------- src/components/street-align.js | 75 +++++--------------------------- 2 files changed, 57 insertions(+), 87 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index c7efc543..f00f23f1 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -50,6 +50,8 @@ AFRAME.registerComponent('managed-street', { if (this.data.enableAlignment && !this.el.hasAttribute('street-align')) { this.el.setAttribute('street-align', ''); } + + this.setupEventDispatcher(); }, /** * Inserts a new street segment at the specified index @@ -130,35 +132,55 @@ AFRAME.registerComponent('managed-street', { return segmentEl; }, - setupMutationObserver: function () { - // Create mutation observer - if (this.observer) { - this.observer.disconnect(); - } - this.observer = new MutationObserver((mutations) => { - let needsReflow = false; - + setupEventDispatcher: function () { + // Mutation observer for add/remove + const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { - if (mutation.type === 'childList' && mutation.removedNodes.length > 0) { - // Check if any of the removed nodes were street segments - mutation.removedNodes.forEach((node) => { - if (node.hasAttribute && node.hasAttribute('street-segment')) { - needsReflow = true; - } + if (mutation.type === 'childList') { + const addedSegments = Array.from(mutation.addedNodes).filter( + (node) => node.hasAttribute && node.hasAttribute('street-segment') + ); + const removedSegments = Array.from(mutation.removedNodes).filter( + (node) => node.hasAttribute && node.hasAttribute('street-segment') + ); + + // Add listeners to new segments + addedSegments.forEach((segment) => { + segment.addEventListener( + 'segment-width-changed', + this.onSegmentWidthChanged.bind(this) + ); }); + + // Remove listeners from removed segments + removedSegments.forEach((segment) => { + segment.removeEventListener( + 'segment-width-changed', + this.onSegmentWidthChanged.bind(this) + ); + }); + + if (addedSegments.length || removedSegments.length) { + this.el.emit('segments-changed', { + changeType: 'structure', + added: addedSegments, + removed: removedSegments + }); + } } }); - - // If segments were removed, trigger reflow - if (needsReflow) { - this.refreshManagedEntities(); - this.createOrUpdateJustifiedDirtBox(); - } }); - // Start observing the managed-street element - this.observer.observe(this.el, { - childList: true // watch for child additions/removals + observer.observe(this.el, { childList: true }); + }, + onSegmentWidthChanged: function (event) { + console.log('segment width changed handler called', event); + this.el.emit('segments-changed', { + changeType: 'property', + property: 'width', + segment: event.target, + oldValue: event.detail.oldWidth, + newValue: event.detail.newWidth }); }, update: function (oldData) { @@ -217,7 +239,6 @@ AFRAME.registerComponent('managed-street', { this.managedEntities = Array.from( this.el.querySelectorAll('[street-segment]') ); - this.setupMutationObserver(); // calculate actual width this.actualWidth = this.managedEntities.reduce((sum, segment) => { return sum + (segment.getAttribute('street-segment')?.width || 0); diff --git a/src/components/street-align.js b/src/components/street-align.js index d35a2712..58bb2deb 100644 --- a/src/components/street-align.js +++ b/src/components/street-align.js @@ -15,76 +15,24 @@ AFRAME.registerComponent('street-align', { }, init: function () { - this.monitoredSegments = []; + // Listen for any segment changes from managed-street + this.el.addEventListener('segments-changed', () => this.realignStreet()); - this.boundWidthChangedHandler = this.onSegmentWidthChanged.bind(this); - - // Initial setup - this.refreshMonitoredSegments(); // This now handles initial segment listeners - this.setupMutationObserver(); + // Initial alignment this.realignStreet(); - // TODO: This is bad -- for when loading from saved scene, add set timeout to refresh the segments and realign + // TODO: Still need this for loading from saved scene setTimeout(() => { - this.refreshMonitoredSegments(); this.realignStreet(); }, 2000); }, - setupSegmentListeners: function () { - // Set up listeners for all existing segments - const segments = this.el.querySelectorAll('[street-segment]'); - segments.forEach((segment) => this.addSegmentListener(segment)); - }, - - addSegmentListener: function (segment) { - // Listen for width changes - segment.addEventListener( - 'segment-width-changed', - this.boundWidthChangedHandler - ); - }, - - removeSegmentListener: function (segment) { - // Remove listeners - segment.removeEventListener( - 'segment-width-changed', - this.boundWidthChangedHandler - ); - const index = this.monitoredSegments.indexOf(segment); - if (index > -1) { - this.monitoredSegments.splice(index, 1); - } - }, - onSegmentWidthChanged: function (event) { console.log('segment width changed handler called', event); this.refreshMonitoredSegments(); this.realignStreet(); }, - refreshMonitoredSegments: function () { - // Clear existing listeners - this.monitoredSegments.forEach((segment) => { - this.removeSegmentListener(segment); - }); - - // Reset the list - this.monitoredSegments = []; - - // Reset and repopulate the list - this.monitoredSegments = Array.from( - this.el.querySelectorAll('[street-segment]') - ); - - // Add new listeners - const segments = this.el.querySelectorAll('[street-segment]'); - segments.forEach((segment) => { - this.addSegmentListener(segment); - }); - console.log('monitored segments', this.monitoredSegments); - }, - update: function (oldData) { const data = this.data; const diff = AFRAME.utils.diff(oldData, data); @@ -134,10 +82,13 @@ AFRAME.registerComponent('street-align', { realignStreet: function () { const data = this.data; - if (this.monitoredSegments.length === 0) return; + + // Get all segments + const segments = Array.from(this.el.querySelectorAll('[street-segment]')); + if (segments.length === 0) return; // Calculate total width - const totalWidth = this.monitoredSegments.reduce((sum, segment) => { + const totalWidth = segments.reduce((sum, segment) => { return sum + (segment.getAttribute('street-segment')?.width || 0); }, 0); console.log('total width', totalWidth); @@ -161,7 +112,7 @@ AFRAME.registerComponent('street-align', { } // Position segments - this.monitoredSegments.forEach((segment) => { + segments.forEach((segment) => { const width = segment.getAttribute('street-segment')?.width; const currentPos = segment.getAttribute('position'); @@ -178,9 +129,7 @@ AFRAME.registerComponent('street-align', { }, remove: function () { - if (this.observer) { - this.observer.disconnect(); - this.observer = null; - } + // Clean up event listener + this.el.removeEventListener('segments-changed', this.realignStreet); } }); From 20142dcf2f1286e540311715588409e026407158 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 12:30:51 -0800 Subject: [PATCH 29/53] separate dirtbox to street-ground also fix up event dispatcher concept --- src/components/managed-street.js | 90 ++++++++++---------------------- src/components/street-align.js | 11 ++-- src/components/street-ground.js | 90 ++++++++++++++++++++++++++++++++ src/components/street-segment.js | 5 -- src/index.js | 1 + 5 files changed, 125 insertions(+), 72 deletions(-) create mode 100644 src/components/street-ground.js diff --git a/src/components/managed-street.js b/src/components/managed-street.js index f00f23f1..c22346db 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -39,6 +39,10 @@ AFRAME.registerComponent('managed-street', { enableAlignment: { type: 'boolean', default: true + }, + showGround: { + type: 'boolean', + default: true } }, init: function () { @@ -50,8 +54,25 @@ AFRAME.registerComponent('managed-street', { if (this.data.enableAlignment && !this.el.hasAttribute('street-align')) { this.el.setAttribute('street-align', ''); } + if (this.data.showGround && !this.el.hasAttribute('street-ground')) { + this.el.setAttribute('street-ground', ''); + } this.setupEventDispatcher(); + + setTimeout(() => { + this.attachListenersToExistingSegments(); + }, 0); + }, + attachListenersToExistingSegments: function () { + const segments = this.el.querySelectorAll('[street-segment]'); + segments.forEach((segment) => { + console.log('Attaching width change listener to existing segment'); + segment.addEventListener( + 'segment-width-changed', + this.onSegmentWidthChanged.bind(this) + ); + }); }, /** * Inserts a new street segment at the specified index @@ -123,9 +144,6 @@ AFRAME.registerComponent('managed-street', { }, 0); this.el.setAttribute('managed-street', 'width', totalWidth); - // Update the dirt box - this.createOrUpdateJustifiedDirtBox(); - // If we have a previous segment, check if we need to add stripe separators // TODO: Check striping here in the future }); @@ -133,6 +151,11 @@ AFRAME.registerComponent('managed-street', { return segmentEl; }, setupEventDispatcher: function () { + // Remove if existing mutation observer + if (this.observer) { + this.observer.disconnect(); + } + // Mutation observer for add/remove const observer = new MutationObserver((mutations) => { mutations.forEach((mutation) => { @@ -194,17 +217,13 @@ AFRAME.registerComponent('managed-street', { const dataDiffKeys = Object.keys(dataDiff); - if (dataDiffKeys.includes('width')) { - this.createOrUpdateJustifiedDirtBox(); - } - if (dataDiffKeys.includes('length')) { this.refreshManagedEntities(); this.applyLength(); - this.createOrUpdateJustifiedDirtBox(); } // if the value of length changes, then we need to update the length of all the child objects // we need to get a list of all the child objects whose length we need to change + this.setupEventDispatcher(); }, refreshFromSource: function () { const data = this.data; @@ -245,60 +264,6 @@ AFRAME.registerComponent('managed-street', { }, 0); console.log('actual width', this.actualWidth); }, - createOrUpdateJustifiedDirtBox: function () { - console.log('createOrUpdateJustifiedDirtBox'); - const data = this.data; - const streetWidth = data.width; - if (!streetWidth) { - return; - } - const streetLength = data.length; - if (!this.justifiedDirtBox) { - // try to find an existing dirt box - this.justifiedDirtBox = this.el.querySelector('.dirtbox'); - } - if (!this.justifiedDirtBox) { - // create new brown box to represent ground underneath street - const dirtBox = document.createElement('a-box'); - dirtBox.classList.add('dirtbox'); - this.el.append(dirtBox); - this.justifiedDirtBox = dirtBox; - dirtBox.setAttribute('material', `color: ${window.STREET.colors.brown};`); - dirtBox.setAttribute('data-layer-name', 'Underground'); - dirtBox.setAttribute('data-no-transform', ''); - dirtBox.setAttribute('data-ignore-raycaster', ''); - } - this.justifiedDirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 - this.justifiedDirtBox.setAttribute('width', this.actualWidth); - this.justifiedDirtBox.setAttribute('depth', streetLength - 0.2); // depth is length - 0.1 on each side - - // instead of data.justifyWidth, use [street-align] component to get width justification - const alignWidth = this.el.getAttribute('street-align')?.width; - const alignLength = this.el.getAttribute('street-align')?.length; - - // set starting xPosition for width justification - let xPosition = 0; // default for center justified - if (alignWidth === 'left') { - xPosition = streetWidth / 2; - } - if (alignWidth === 'right') { - xPosition = -streetWidth / 2; - } - - // set z value for length justification - let zPosition = 0; // default for middle justified - if (alignLength === 'start') { - zPosition = -streetLength / 2; - } - if (alignLength === 'end') { - zPosition = streetLength / 2; - } - - this.justifiedDirtBox.setAttribute( - 'position', - `${xPosition} -1 ${zPosition}` - ); - }, parseStreetObject: function (streetObject) { // reset and delete all existing entities this.remove(); @@ -476,7 +441,6 @@ AFRAME.registerComponent('managed-street', { // When all entities are loaded, do something with them this.allLoadedPromise.then(() => { this.refreshManagedEntities(); - this.createOrUpdateJustifiedDirtBox(); AFRAME.INSPECTOR.selectEntity(this.el); }); } catch (error) { diff --git a/src/components/street-align.js b/src/components/street-align.js index 58bb2deb..dd1ada50 100644 --- a/src/components/street-align.js +++ b/src/components/street-align.js @@ -1,6 +1,7 @@ /* global AFRAME */ AFRAME.registerComponent('street-align', { + dependencies: ['managed-street'], schema: { width: { default: 'center', @@ -24,12 +25,11 @@ AFRAME.registerComponent('street-align', { // TODO: Still need this for loading from saved scene setTimeout(() => { this.realignStreet(); - }, 2000); + }, 0); }, onSegmentWidthChanged: function (event) { console.log('segment width changed handler called', event); - this.refreshMonitoredSegments(); this.realignStreet(); }, @@ -39,7 +39,11 @@ AFRAME.registerComponent('street-align', { // Only realign if width or length alignment changed if (diff.width !== undefined || diff.length !== undefined) { - // this.alignStreetSegments(); + this.el.emit('alignment-changed', { + changeType: 'alignment', + oldData: oldData, + newData: data + }); this.realignStreet(); } }, @@ -69,7 +73,6 @@ AFRAME.registerComponent('street-align', { }); if (needsReflow) { - this.refreshMonitoredSegments(); this.realignStreet(); } }); diff --git a/src/components/street-ground.js b/src/components/street-ground.js new file mode 100644 index 00000000..00edeb97 --- /dev/null +++ b/src/components/street-ground.js @@ -0,0 +1,90 @@ +AFRAME.registerComponent('street-ground', { + dependencies: ['managed-street', 'street-align'], + + init: function () { + // Listen for any changes from managed-street + this.el.addEventListener('segments-changed', () => + this.createOrUpdateDirtbox() + ); + + // Listen for alignment changes + this.el.addEventListener('alignment-changed', () => + this.createOrUpdateDirtbox() + ); + + // Create initial dirtbox + this.createOrUpdateDirtbox(); + + setTimeout(() => { + this.createOrUpdateDirtbox(); + }, 0); + }, + + createOrUpdateDirtbox: function () { + console.log('dirtbox fired update'); + // Find or create dirtbox element + if (!this.dirtbox) { + this.dirtbox = this.el.querySelector('.dirtbox'); + } + if (!this.dirtbox) { + this.dirtbox = document.createElement('a-box'); + this.dirtbox.classList.add('autocreated'); + this.dirtbox.classList.add('.dirtbox'); + this.el.append(this.dirtbox); + + this.dirtbox.setAttribute( + 'material', + `color: ${window.STREET.colors.brown};` + ); + this.dirtbox.setAttribute('data-layer-name', 'Underground'); + this.dirtbox.setAttribute('data-no-transform', ''); + this.dirtbox.setAttribute('data-ignore-raycaster', ''); + } + + // Get all segments + const segments = Array.from(this.el.querySelectorAll('[street-segment]')); + if (segments.length === 0) return; + + const totalWidth = segments.reduce((sum, segment) => { + return sum + (segment.getAttribute('street-segment')?.width || 0); + }, 0); + const streetLength = this.el.getAttribute('managed-street')?.length || 0; + + // Update dirtbox dimensions + this.dirtbox.setAttribute('height', 2); + this.dirtbox.setAttribute('width', totalWidth); + this.dirtbox.setAttribute('depth', streetLength - 0.2); + + // Get alignment from street-align component + const streetAlign = this.el.components['street-align']; + const alignWidth = streetAlign?.data.width || 'center'; + const alignLength = streetAlign?.data.length || 'start'; + + // Calculate position based on alignment + let xPosition = 0; + if (alignWidth === 'center') { + xPosition = 0; + } else if (alignWidth === 'left') { + xPosition = totalWidth / 2; + } else if (alignWidth === 'right') { + xPosition = -totalWidth / 2; + } + + let zPosition = 0; + if (alignLength === 'start') { + zPosition = -streetLength / 2; + } else if (alignLength === 'end') { + zPosition = streetLength / 2; + } + + this.dirtbox.setAttribute('position', `${xPosition} -1 ${zPosition}`); + }, + + remove: function () { + // Clean up + if (this.dirtbox) { + this.dirtbox.parentNode.removeChild(this.dirtbox); + } + this.el.removeEventListener('segments-changed', this.createOrUpdateDirtbox); + } +}); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 2f449751..4bdaed89 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -331,11 +331,6 @@ AFRAME.registerComponent('street-segment', { oldWidth: oldData.width, newWidth: data.width }); - - this.el.parentNode.components['managed-street'].refreshManagedEntities(); - this.el.parentNode.components[ - 'managed-street' - ].createOrUpdateJustifiedDirtBox(); } }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units diff --git a/src/index.js b/src/index.js index a3f17487..00e5ad20 100644 --- a/src/index.js +++ b/src/index.js @@ -29,6 +29,7 @@ require('./components/street-generated-pedestrians.js'); require('./components/street-generated-rail.js'); require('./components/street-generated-clones.js'); require('./components/street-align.js'); +require('./components/street-ground.js'); require('./editor/index.js'); var firebase = require('./editor/services/firebase.js'); From 3556175f74829bd1c4b0d5e9328c21c32fcdab5e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 14:51:19 -0800 Subject: [PATCH 30/53] simplify id in layersData --- .../components/AddLayerPanel/layersData.js | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 7aa6f3c4..6704097a 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -7,7 +7,6 @@ export const streetLayersData = [ requiresPro: true, icon: 'ui_assets/cards/icons/3dst24.png', description: 'Create 90º intersection entity.', - id: 1, handlerFunction: createFunctions.createIntersection }, { @@ -16,7 +15,6 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Create an additional Streetmix street in your 3DStreet scene without replacing any existing streets.', - id: 2, handlerFunction: createFunctions.createStreetmixStreet }, { @@ -24,7 +22,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-40-24.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', - id: 3, handlerFunction: createFunctions.create40ftRightOfWay }, { @@ -32,7 +29,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-60-36.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', - id: 4, handlerFunction: createFunctions.create60ftRightOfWay }, { @@ -40,7 +36,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-80-56.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 80ft Right of Way / 56ft Roadway Width', - id: 5, handlerFunction: createFunctions.create80ftRightOfWay }, { @@ -48,7 +43,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-94-70.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 94ft Right of Way / 70ft Roadway Width', - id: 6, handlerFunction: createFunctions.create94ftRightOfWay }, { @@ -56,7 +50,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-150-124.jpg', icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Premade Street 150ft Right of Way / 124ft Roadway Width', - id: 7, handlerFunction: createFunctions.create150ftRightOfWay }, { @@ -66,7 +59,6 @@ export const streetLayersData = [ icon: 'ui_assets/cards/icons/streetmix24.png', description: 'Create a new street from Streetmix URL using the Managed Street component.', - id: 8, handlerFunction: createFunctions.createManagedStreetFromStreetmixURLPrompt }, { @@ -74,7 +66,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-40-24.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 40ft Right of Way / 24ft Roadway Width', - id: 9, handlerFunction: createFunctions.create40ftRightOfWayManagedStreet }, { @@ -82,7 +73,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-60-36.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 60ft Right of Way / 36ft Roadway Width', - id: 10, handlerFunction: createFunctions.create60ftRightOfWayManagedStreet }, { @@ -90,7 +80,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-80-56.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 80ft Right of Way / 56ft Roadway Width', - id: 11, handlerFunction: createFunctions.create80ftRightOfWayManagedStreet }, { @@ -98,7 +87,6 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-94-70.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 94ft Right of Way / 70ft Roadway Width', - id: 12, handlerFunction: createFunctions.create94ftRightOfWayManagedStreet }, { @@ -106,10 +94,9 @@ export const streetLayersData = [ img: 'ui_assets/cards/street-preset-150-124.jpg', icon: 'ui_assets/cards/icons/3dst24.png', description: 'Premade Street 150ft Right of Way / 124ft Roadway Width', - id: 13, handlerFunction: createFunctions.create150ftRightOfWayManagedStreet } -]; +].map((layer, index) => ({ ...layer, id: index + 1 })); export const customLayersData = [ { @@ -119,7 +106,6 @@ export const customLayersData = [ requiresPro: true, description: 'Create entity with svg-extruder component, that accepts a svgString and creates a new entity with geometry extruded from the svg and applies the default mixin material grass.', - id: 1, handlerFunction: createFunctions.createSvgExtrudedEntity }, { @@ -129,7 +115,6 @@ export const customLayersData = [ icon: '', description: 'Create entity with model from path for a glTF (or Glb) file hosted on any publicly accessible HTTP server.', - id: 2, handlerFunction: createFunctions.createCustomModel }, { @@ -139,7 +124,6 @@ export const customLayersData = [ icon: '', description: 'Create entity with A-Frame primitive geometry. Geometry type could be changed in properties panel.', - id: 3, handlerFunction: createFunctions.createPrimitiveGeometry }, { @@ -149,7 +133,6 @@ export const customLayersData = [ icon: 'ui_assets/cards/icons/gallery24.png', description: 'Place an image such as a sign, reference photo, custom map, etc.', - id: 4, handlerFunction: createFunctions.createImageEntity } -]; +].map((layer, index) => ({ ...layer, id: index + 1 })); From c2ec7a21ab2bd6bad173e1d1df3a483c54bb80a7 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 15:09:49 -0800 Subject: [PATCH 31/53] polygon offset as component --- src/components/managed-street.js | 12 +--- src/components/polygon-offset.js | 71 +++++++++++++++++++++ src/components/street-generated-stencil.js | 19 +++--- src/components/street-generated-striping.js | 22 ++++--- src/index.js | 1 + 5 files changed, 97 insertions(+), 28 deletions(-) create mode 100644 src/components/polygon-offset.js diff --git a/src/components/managed-street.js b/src/components/managed-street.js index b7e974d7..828519a9 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -238,15 +238,9 @@ AFRAME.registerComponent('managed-street', { `${xPosition} -1 ${zPosition}` ); - this.justifiedDirtBox.addEventListener('loaded', () => { - // Fix z fighting issue - const mesh = this.justifiedDirtBox.getObject3D('mesh'); - if (mesh) { - const material = mesh.material; - material.polygonOffset = true; - material.polygonOffsetFactor = 4; - material.polygonOffsetUnits = 4; - } + this.justifiedDirtBox.setAttribute('polygon-offset', { + factor: 4, + units: 4 }); }, parseStreetObject: function (streetObject) { diff --git a/src/components/polygon-offset.js b/src/components/polygon-offset.js new file mode 100644 index 00000000..8fc3e165 --- /dev/null +++ b/src/components/polygon-offset.js @@ -0,0 +1,71 @@ +// Component to fix z-fighting by adding polygon offset to meshes +AFRAME.registerComponent('polygon-offset', { + schema: { + // Negative values move fragments closer to camera; factor and units are multiplied together + factor: { type: 'number', default: -2 }, + units: { type: 'number', default: -2 } + }, + + init: function () { + // Bind the update method to maintain correct context + this.updateMesh = this.updateMesh.bind(this); + + // Initial update when mesh is loaded + const mesh = this.el.getObject3D('mesh'); + if (mesh) { + this.updateMesh(mesh); + } + + // Listen for model-loaded event + this.el.addEventListener('model-loaded', (evt) => { + const mesh = this.el.getObject3D('mesh'); + this.updateMesh(mesh); + }); + }, + + updateMesh: function (mesh) { + if (!mesh) return; + + // Function to recursively process materials + const processMaterials = (object) => { + if (object.material) { + // Handle single material + if (!Array.isArray(object.material)) { + this.updateMaterial(object.material); + } else { + // Handle multiple materials + object.material.forEach((material) => { + this.updateMaterial(material); + }); + } + } + if (object.children) { + object.children.forEach((child) => { + processMaterials(child); + }); + } + }; + + // Start processing from the root mesh + processMaterials(mesh); + }, + + updateMaterial: function (material) { + if (!material) return; + + material.polygonOffset = true; + material.polygonOffsetFactor = this.data.factor; + material.polygonOffsetUnits = this.data.units; + + // Ensure material updates + material.needsUpdate = true; + }, + + update: function (oldData) { + // Handle property updates + const mesh = this.el.getObject3D('mesh'); + if (mesh) { + this.updateMesh(mesh); + } + } +}); diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index b8182a25..6b7cfbd4 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -166,19 +166,20 @@ AFRAME.registerComponent('street-generated-stencil', { clone.classList.add('autocreated'); clone.setAttribute('data-no-transform', ''); clone.setAttribute('data-layer-name', `Cloned Model • ${stencilName}`); + clone.setAttribute('polygon-offset', { factor: -2, units: -2 }); this.el.appendChild(clone); this.createdEntities.push(clone); // Fix z fighting issue - clone.addEventListener('loaded', () => { - const mesh = clone.getObject3D('mesh'); - if (mesh) { - const material = mesh.material; - material.polygonOffset = true; - material.polygonOffsetFactor = -2; - material.polygonOffsetUnits = -2; - } - }); + // clone.addEventListener('loaded', () => { + // const mesh = clone.getObject3D('mesh'); + // if (mesh) { + // const material = mesh.material; + // material.polygonOffset = true; + // material.polygonOffsetFactor = -2; + // material.polygonOffsetUnits = -2; + // } + // }); }); } } diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 2badc6c1..a8871e64 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -75,18 +75,20 @@ AFRAME.registerComponent('street-generated-striping', { 'data-layer-name', 'Cloned Striping • ' + stripingTextureId ); + clone.setAttribute('polygon-offset', { factor: -2, units: -2 }); + this.el.appendChild(clone); this.createdEntities.push(clone); - // Fix z fighting issue - clone.addEventListener('loaded', () => { - const mesh = clone.getObject3D('mesh'); - if (mesh) { - const material = mesh.material; - material.polygonOffset = true; - material.polygonOffsetFactor = -2; - material.polygonOffsetUnits = -2; - } - }); + // // Fix z fighting issue + // clone.addEventListener('loaded', () => { + // const mesh = clone.getObject3D('mesh'); + // if (mesh) { + // const material = mesh.material; + // material.polygonOffset = true; + // material.polygonOffsetFactor = -2; + // material.polygonOffsetUnits = -2; + // } + // }); }, calculateStripingMaterial: function (stripingName, length) { // calculate the repeatCount for the material diff --git a/src/index.js b/src/index.js index a217dd5b..b2f5840d 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ require('./components/street-generated-striping.js'); require('./components/street-generated-pedestrians.js'); require('./components/street-generated-rail.js'); require('./components/street-generated-clones.js'); +require('./components/polygon-offset.js'); require('./editor/index.js'); var firebase = require('./editor/services/firebase.js'); From c4431f52612ceb08bc93ac0914784353e6041d56 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 15:16:11 -0800 Subject: [PATCH 32/53] update dirtbox with poly offset --- src/components/street-ground.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/street-ground.js b/src/components/street-ground.js index 00edeb97..900d213d 100644 --- a/src/components/street-ground.js +++ b/src/components/street-ground.js @@ -39,6 +39,10 @@ AFRAME.registerComponent('street-ground', { this.dirtbox.setAttribute('data-layer-name', 'Underground'); this.dirtbox.setAttribute('data-no-transform', ''); this.dirtbox.setAttribute('data-ignore-raycaster', ''); + this.dirtbox.setAttribute('polygon-offset', { + factor: 4, + units: 4 + }); } // Get all segments From 8f9b8459927276c7bdec436849fb1f91ca7a4cbd Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 15:24:26 -0800 Subject: [PATCH 33/53] use three.js traverse --- src/components/polygon-offset.js | 63 ++++++++++++++++++-------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/src/components/polygon-offset.js b/src/components/polygon-offset.js index 8fc3e165..f715ce01 100644 --- a/src/components/polygon-offset.js +++ b/src/components/polygon-offset.js @@ -1,53 +1,39 @@ // Component to fix z-fighting by adding polygon offset to meshes AFRAME.registerComponent('polygon-offset', { schema: { - // Negative values move fragments closer to camera; factor and units are multiplied together + // Negative values move fragments closer to camera factor: { type: 'number', default: -2 }, units: { type: 'number', default: -2 } }, init: function () { - // Bind the update method to maintain correct context - this.updateMesh = this.updateMesh.bind(this); - // Initial update when mesh is loaded const mesh = this.el.getObject3D('mesh'); if (mesh) { - this.updateMesh(mesh); + this.applyPolygonOffsetToObject(mesh); } // Listen for model-loaded event this.el.addEventListener('model-loaded', (evt) => { const mesh = this.el.getObject3D('mesh'); - this.updateMesh(mesh); + this.applyPolygonOffsetToObject(mesh); }); }, - updateMesh: function (mesh) { - if (!mesh) return; + applyPolygonOffsetToObject: function (object3D) { + if (!object3D) return; - // Function to recursively process materials - const processMaterials = (object) => { - if (object.material) { - // Handle single material - if (!Array.isArray(object.material)) { - this.updateMaterial(object.material); - } else { - // Handle multiple materials - object.material.forEach((material) => { + object3D.traverse((obj) => { + if (obj.type === 'Mesh') { + if (Array.isArray(obj.material)) { + obj.material.forEach((material) => { this.updateMaterial(material); }); + } else { + this.updateMaterial(obj.material); } } - if (object.children) { - object.children.forEach((child) => { - processMaterials(child); - }); - } - }; - - // Start processing from the root mesh - processMaterials(mesh); + }); }, updateMaterial: function (material) { @@ -65,7 +51,30 @@ AFRAME.registerComponent('polygon-offset', { // Handle property updates const mesh = this.el.getObject3D('mesh'); if (mesh) { - this.updateMesh(mesh); + this.applyPolygonOffsetToObject(mesh); + } + }, + + remove: function () { + const mesh = this.el.getObject3D('mesh'); + if (mesh) { + mesh.traverse((obj) => { + if (obj.type === 'Mesh') { + if (Array.isArray(obj.material)) { + obj.material.forEach((material) => { + material.polygonOffset = false; + material.polygonOffsetFactor = 0; + material.polygonOffsetUnits = 0; + material.needsUpdate = true; + }); + } else if (obj.material) { + obj.material.polygonOffset = false; + obj.material.polygonOffsetFactor = 0; + obj.material.polygonOffsetUnits = 0; + obj.material.needsUpdate = true; + } + } + }); } } }); From be03808bbd5bc1e8b41e545b75ef83875062641f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 16:06:19 -0800 Subject: [PATCH 34/53] use manage-street event dispatcher --- src/components/managed-street.js | 7 + src/components/street-generated-label.js | 112 ----------- src/components/street-label.js | 228 +++++++++++++++++++++++ src/index.js | 1 + 4 files changed, 236 insertions(+), 112 deletions(-) delete mode 100644 src/components/street-generated-label.js create mode 100644 src/components/street-label.js diff --git a/src/components/managed-street.js b/src/components/managed-street.js index c22346db..f48f4a06 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -43,6 +43,10 @@ AFRAME.registerComponent('managed-street', { showGround: { type: 'boolean', default: true + }, + showLabels: { + type: 'boolean', + default: true } }, init: function () { @@ -57,6 +61,9 @@ AFRAME.registerComponent('managed-street', { if (this.data.showGround && !this.el.hasAttribute('street-ground')) { this.el.setAttribute('street-ground', ''); } + if (this.data.showLabels && !this.el.hasAttribute('street-label')) { + this.el.setAttribute('street-label', ''); + } this.setupEventDispatcher(); diff --git a/src/components/street-generated-label.js b/src/components/street-generated-label.js deleted file mode 100644 index 797f6a99..00000000 --- a/src/components/street-generated-label.js +++ /dev/null @@ -1,112 +0,0 @@ -/* global AFRAME */ - -// WIP make managed street labels from canvas -// assumes existing canvas with id label-canvas -// -// - // AFRAME.registerComponent('draw-canvas', { - // schema: { - // myCanvas: { type: 'string' }, - // managedStreet: { type: 'string' } // json of managed street children - // }, - // init: function () { - // // const objects = this.data.managedStreet.children; - // const objects = JSON.parse(this.data.managedStreet).children; - // this.canvas = document.getElementById(this.data); - // this.ctx = this.canvas.getContext('2d'); - // // Calculate total width from all objects - // const totalWidth = objects.reduce((sum, obj) => sum + obj.width, 0); - // ctx = this.ctx; - // canvas = this.canvas; - // // Set up canvas styling - // ctx.fillStyle = '#ffffff'; - // ctx.fillRect(0, 0, canvas.width, canvas.height); - // ctx.font = '24px Arial'; - // ctx.textAlign = 'center'; - // ctx.textBaseline = 'middle'; - // // Track current x position - // let currentX = 0; - // // Draw each segment - // objects.forEach((obj, index) => { - // // Calculate proportional width for this segment - // const segmentWidth = (obj.width / totalWidth) * canvas.width; - // // Draw segment background with alternating colors - // ctx.fillStyle = index % 2 === 0 ? '#f0f0f0' : '#e0e0e0'; - // ctx.fillRect(currentX, 0, segmentWidth, canvas.height); - // // Draw segment border - // ctx.strokeStyle = '#999999'; - // ctx.beginPath(); - // ctx.moveTo(currentX, 0); - // ctx.lineTo(currentX, canvas.height); - // ctx.stroke(); - // // Draw centered label - // ctx.fillStyle = '#000000'; - // const centerX = currentX + (segmentWidth / 2); - // const centerY = canvas.height / 2; - // // Format width number for display - // const label = obj.width.toLocaleString(); - // // Draw label with background for better readability - // const textMetrics = ctx.measureText(label); - // const textHeight = 30; // Approximate height of text - // const padding = 10; - // // Draw text background - // ctx.fillStyle = 'rgba(255, 255, 255, 0.8)'; - // ctx.fillRect( - // centerX - (textMetrics.width / 2) - padding, - // centerY - (textHeight / 2) - padding, - // textMetrics.width + (padding * 2), - // textHeight + (padding * 2) - // ); - // // Draw text - // ctx.fillStyle = '#000000'; - // ctx.fillText(label, centerX, centerY); - // // Update x position for next segment - // currentX += segmentWidth; - // }); - // // Draw final border - // ctx.strokeStyle = '#999999'; - // ctx.beginPath(); - // ctx.moveTo(canvas.width, 0); - // ctx.lineTo(canvas.width, canvas.height); - // ctx.stroke(); - // // Draw on canvas... - // } - // }); - // - } -}); diff --git a/src/components/street-label.js b/src/components/street-label.js new file mode 100644 index 00000000..c3b0d354 --- /dev/null +++ b/src/components/street-label.js @@ -0,0 +1,228 @@ +/* global AFRAME */ + +AFRAME.registerComponent('street-label', { + dependencies: ['managed-street'], + + schema: { + heightOffset: { type: 'number', default: -2 }, + rotation: { type: 'vec3', default: { x: -30, y: 0, z: 0 } }, + zOffset: { type: 'number', default: 1 }, + labelHeight: { type: 'number', default: 2.5 }, + baseCanvasWidth: { type: 'number', default: 4096 } + }, + + init: function () { + this.createdEntities = []; + this.canvas = null; + this.ctx = null; + + // Create and setup canvas + this.createAndSetupCanvas(); + + // Listen for segment changes + this.el.addEventListener('segments-changed', this.updateLabels.bind(this)); + + // Initial update + this.updateLabels(); + + // Handle loading from saved scene + setTimeout(() => { + this.updateLabels(); + }, 0); + }, + + updateLabels: function () { + const segments = Array.from(this.el.querySelectorAll('[street-segment]')); + if (segments.length === 0) return; + + const widthsArray = []; + const labelsArray = []; + + segments.forEach((segmentEl) => { + if (!segmentEl.getAttribute('street-segment')) return; + + const segmentWidth = segmentEl.getAttribute('street-segment').width; + widthsArray.push(segmentWidth); + labelsArray.push(segmentEl.getAttribute('data-layer-name') || ''); + }); + + if (widthsArray.length !== labelsArray.length) { + console.error('Mismatch between widths and labels arrays'); + return; + } + + const totalWidth = widthsArray.reduce( + (sum, width) => sum + parseFloat(width), + 0 + ); + + this.updateCanvasDimensions(totalWidth); + this.drawLabels(widthsArray, labelsArray, totalWidth); + this.createLabelPlane(totalWidth); + }, + + createAndSetupCanvas: function () { + this.canvas = document.createElement('canvas'); + this.canvas.id = 'street-label-canvas'; + this.canvas.style.display = 'none'; + document.body.appendChild(this.canvas); + this.ctx = this.canvas.getContext('2d'); + }, + + updateCanvasDimensions: function (totalWidth) { + const aspectRatio = totalWidth / this.data.labelHeight; + + this.canvas.width = this.data.baseCanvasWidth; + this.canvas.height = Math.round(this.data.baseCanvasWidth / aspectRatio); + + this.fontSize = Math.round(this.canvas.height * 0.18); + this.subFontSize = Math.round(this.canvas.height * 0.14); + }, + + wrapText: function (text, maxWidth) { + const words = text.split(' '); + const lines = []; + let currentLine = words[0]; + + for (let i = 1; i < words.length; i++) { + const word = words[i]; + const width = this.ctx.measureText(currentLine + ' ' + word).width; + + if (width < maxWidth) { + currentLine += ' ' + word; + } else { + lines.push(currentLine); + currentLine = word; + } + } + lines.push(currentLine); + return lines; + }, + + drawMultilineText: function (lines, x, y, lineHeight) { + const totalHeight = (lines.length - 1) * lineHeight; + const startY = y - totalHeight / 2; + + lines.forEach((line, index) => { + const yPos = startY + index * lineHeight; + this.ctx.fillText(line, x, yPos); + }); + }, + + drawLabels: function (widthsArray, labelsArray, totalWidth) { + const { ctx, canvas } = this; + + // Clear canvas + ctx.clearRect(0, 0, canvas.width, canvas.height); + ctx.fillStyle = '#ffffff'; + ctx.fillRect(0, 0, canvas.width, canvas.height); + + let currentX = 0; + ctx.textAlign = 'center'; + ctx.textBaseline = 'middle'; + + widthsArray.forEach((width, index) => { + const segmentWidth = (parseFloat(width) / totalWidth) * canvas.width; + const maxLabelWidth = segmentWidth * 0.9; + + // Draw segment background + ctx.fillStyle = index % 2 === 0 ? '#f0f0f0' : '#e0e0e0'; + ctx.fillRect(currentX, 0, segmentWidth, canvas.height); + + // Draw segment border + ctx.strokeStyle = '#999999'; + ctx.beginPath(); + ctx.moveTo(currentX, 0); + ctx.lineTo(currentX, canvas.height); + ctx.stroke(); + + // Draw width value + ctx.fillStyle = '#000000'; + ctx.font = `${this.fontSize}px Arial`; + const centerX = currentX + segmentWidth / 2; + const centerY = canvas.height / 2 - 50; + + const widthText = parseFloat(width).toFixed(1) + 'm'; + ctx.fillText(widthText, centerX, centerY - this.fontSize * 0.8); + + // Draw wrapped label text + if (labelsArray[index]) { + ctx.font = `${this.subFontSize}px Arial`; + const lines = this.wrapText(labelsArray[index], maxLabelWidth); + const lineHeight = this.subFontSize * 1.2; + this.drawMultilineText( + lines, + centerX, + centerY + this.fontSize * 0.4 + 75, + lineHeight + ); + } + + currentX += segmentWidth; + }); + + // Draw final border + ctx.strokeStyle = '#999999'; + ctx.beginPath(); + ctx.moveTo(canvas.width, 0); + ctx.lineTo(canvas.width, canvas.height); + ctx.stroke(); + }, + + createLabelPlane: function (totalWidth) { + // Remove existing entities + this.createdEntities.forEach((entity) => { + if (entity.parentNode) { + entity.parentNode.removeChild(entity); + } + }); + this.createdEntities = []; + + // Create new label plane + const plane = document.createElement('a-entity'); + + plane.setAttribute('geometry', { + primitive: 'plane', + width: totalWidth, + height: this.data.labelHeight + }); + + plane.setAttribute('material', { + src: '#street-label-canvas', + transparent: true, + alphaTest: 0.5 + }); + + plane.setAttribute( + 'position', + `0 ${this.data.heightOffset} ${this.data.zOffset}` + ); + plane.setAttribute( + 'rotation', + `${this.data.rotation.x} ${this.data.rotation.y} ${this.data.rotation.z}` + ); + plane.setAttribute('data-layer-name', 'Segment Labels'); + plane.classList.add('autocreated'); + + this.el.appendChild(plane); + this.createdEntities.push(plane); + }, + + remove: function () { + // Clean up canvas + if (this.canvas && this.canvas.parentNode) { + this.canvas.parentNode.removeChild(this.canvas); + } + + // Remove created entities + this.createdEntities.forEach((entity) => { + if (entity.parentNode) { + entity.parentNode.removeChild(entity); + } + }); + this.createdEntities = []; + + // Remove event listener + this.el.removeEventListener('segments-changed', this.updateLabels); + } +}); diff --git a/src/index.js b/src/index.js index c3906c2e..b223b2ad 100644 --- a/src/index.js +++ b/src/index.js @@ -31,6 +31,7 @@ require('./components/street-generated-clones.js'); require('./components/polygon-offset.js'); require('./components/street-align.js'); require('./components/street-ground.js'); +require('./components/street-label.js'); require('./editor/index.js'); var firebase = require('./editor/services/firebase.js'); From d397f46af7aff153561689667f3124b70ce29bb1 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 21:17:43 -0800 Subject: [PATCH 35/53] updateLabels on update --- src/components/street-label.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/street-label.js b/src/components/street-label.js index c3b0d354..308463a3 100644 --- a/src/components/street-label.js +++ b/src/components/street-label.js @@ -31,6 +31,9 @@ AFRAME.registerComponent('street-label', { }, 0); }, + update: function () { + this.updateLabels(); + }, updateLabels: function () { const segments = Array.from(this.el.querySelectorAll('[street-segment]')); if (segments.length === 0) return; From 7435a83fa4d45e545d92081fed49d173c5042e23 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 22:18:50 -0800 Subject: [PATCH 36/53] default no rotation --- src/components/street-label.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/street-label.js b/src/components/street-label.js index 308463a3..437d1f57 100644 --- a/src/components/street-label.js +++ b/src/components/street-label.js @@ -5,7 +5,7 @@ AFRAME.registerComponent('street-label', { schema: { heightOffset: { type: 'number', default: -2 }, - rotation: { type: 'vec3', default: { x: -30, y: 0, z: 0 } }, + rotation: { type: 'vec3', default: { x: 0, y: 0, z: 0 } }, zOffset: { type: 'number', default: 1 }, labelHeight: { type: 'number', default: 2.5 }, baseCanvasWidth: { type: 'number', default: 4096 } From b0958dff65270717cc01e7139c53efc3af0d7541 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 22:45:07 -0800 Subject: [PATCH 37/53] respect alignment x position --- src/components/street-label.js | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/components/street-label.js b/src/components/street-label.js index 437d1f57..9a7f76da 100644 --- a/src/components/street-label.js +++ b/src/components/street-label.js @@ -1,7 +1,7 @@ /* global AFRAME */ AFRAME.registerComponent('street-label', { - dependencies: ['managed-street'], + dependencies: ['managed-street', 'street-align'], schema: { heightOffset: { type: 'number', default: -2 }, @@ -22,6 +22,9 @@ AFRAME.registerComponent('street-label', { // Listen for segment changes this.el.addEventListener('segments-changed', this.updateLabels.bind(this)); + // Listen for alignment changes + this.el.addEventListener('alignment-changed', this.updateLabels.bind(this)); + // Initial update this.updateLabels(); @@ -196,9 +199,23 @@ AFRAME.registerComponent('street-label', { alphaTest: 0.5 }); + // Get alignment from street-align component + const streetAlign = this.el.components['street-align']; + const alignWidth = streetAlign?.data.width || 'center'; + + // Calculate position based on alignment + let xPosition = 0; + if (alignWidth === 'center') { + xPosition = 0; + } else if (alignWidth === 'left') { + xPosition = totalWidth / 2; + } else if (alignWidth === 'right') { + xPosition = -totalWidth / 2; + } + plane.setAttribute( 'position', - `0 ${this.data.heightOffset} ${this.data.zOffset}` + `${xPosition} ${this.data.heightOffset} ${this.data.zOffset}` ); plane.setAttribute( 'rotation', @@ -227,5 +244,6 @@ AFRAME.registerComponent('street-label', { // Remove event listener this.el.removeEventListener('segments-changed', this.updateLabels); + this.el.removeEventListener('alignment-changed', this.updateLabels); } }); From 87c497fb6ab83875b5830f653f4fd1e750b66963 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 22:57:18 -0800 Subject: [PATCH 38/53] support alignment z position --- src/components/street-label.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/components/street-label.js b/src/components/street-label.js index 9a7f76da..ff85c0aa 100644 --- a/src/components/street-label.js +++ b/src/components/street-label.js @@ -202,8 +202,12 @@ AFRAME.registerComponent('street-label', { // Get alignment from street-align component const streetAlign = this.el.components['street-align']; const alignWidth = streetAlign?.data.width || 'center'; + const alignLength = streetAlign?.data.length || 'start'; - // Calculate position based on alignment + // Get street length from managed-street component + const streetLength = this.el.getAttribute('managed-street')?.length || 0; + + // Calculate x position based on width alignment let xPosition = 0; if (alignWidth === 'center') { xPosition = 0; @@ -213,9 +217,17 @@ AFRAME.registerComponent('street-label', { xPosition = -totalWidth / 2; } + // Calculate z position based on length alignment + let zPosition = this.data.zOffset; // for 'start' alignment + if (alignLength === 'middle') { + zPosition = streetLength / 2 + this.data.zOffset; + } else if (alignLength === 'end') { + zPosition = streetLength + this.data.zOffset; + } + plane.setAttribute( 'position', - `${xPosition} ${this.data.heightOffset} ${this.data.zOffset}` + `${xPosition} ${this.data.heightOffset} ${zPosition}` ); plane.setAttribute( 'rotation', From d5d1057442a5101f0bd5f0dcb170b69bd60eddbd Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 23:33:39 -0800 Subject: [PATCH 39/53] enabled bool on street-label --- src/components/managed-street.js | 6 +----- src/components/street-label.js | 25 ++++++++++++++++++++++--- 2 files changed, 23 insertions(+), 8 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index f48f4a06..fc680012 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -43,10 +43,6 @@ AFRAME.registerComponent('managed-street', { showGround: { type: 'boolean', default: true - }, - showLabels: { - type: 'boolean', - default: true } }, init: function () { @@ -61,7 +57,7 @@ AFRAME.registerComponent('managed-street', { if (this.data.showGround && !this.el.hasAttribute('street-ground')) { this.el.setAttribute('street-ground', ''); } - if (this.data.showLabels && !this.el.hasAttribute('street-label')) { + if (!this.el.hasAttribute('street-label')) { this.el.setAttribute('street-label', ''); } diff --git a/src/components/street-label.js b/src/components/street-label.js index ff85c0aa..66b9ba72 100644 --- a/src/components/street-label.js +++ b/src/components/street-label.js @@ -4,6 +4,7 @@ AFRAME.registerComponent('street-label', { dependencies: ['managed-street', 'street-align'], schema: { + enabled: { type: 'boolean', default: true }, heightOffset: { type: 'number', default: -2 }, rotation: { type: 'vec3', default: { x: 0, y: 0, z: 0 } }, zOffset: { type: 'number', default: 1 }, @@ -30,13 +31,31 @@ AFRAME.registerComponent('street-label', { // Handle loading from saved scene setTimeout(() => { - this.updateLabels(); + if (this.data.enabled) { + this.updateLabels(); + } }, 0); }, - update: function () { - this.updateLabels(); + update: function (oldData) { + if (oldData && this.data.enabled !== oldData.enabled) { + if (!this.data.enabled) { + // Hide existing labels + this.createdEntities.forEach((entity) => { + entity.setAttribute('visible', false); + }); + } else { + // Show and update labels + this.createdEntities.forEach((entity) => { + entity.setAttribute('visible', true); + }); + this.updateLabels(); + } + } else if (this.data.enabled) { + this.updateLabels(); + } }, + updateLabels: function () { const segments = Array.from(this.el.querySelectorAll('[street-segment]')); if (segments.length === 0) return; From 6ff63b98a618f88b5590a2633b6b8d93fd9f632f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 23:49:03 -0800 Subject: [PATCH 40/53] polygon offset cleanup --- src/components/street-generated-stencil.js | 11 +---------- src/components/street-generated-striping.js | 13 ++----------- 2 files changed, 3 insertions(+), 21 deletions(-) diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 57789891..542b6dfd 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -199,19 +199,10 @@ AFRAME.registerComponent('street-generated-stencil', { clone.setAttribute('data-no-transform', ''); clone.setAttribute('data-layer-name', `Cloned Model • ${stencilName}`); clone.setAttribute('data-parent-component', this.attrName); + clone.setAttribute('polygon-offset', { factor: -2, units: -2 }); this.el.appendChild(clone); this.createdEntities.push(clone); - // Fix z fighting issue - // clone.addEventListener('loaded', () => { - // const mesh = clone.getObject3D('mesh'); - // if (mesh) { - // const material = mesh.material; - // material.polygonOffset = true; - // material.polygonOffsetFactor = -2; - // material.polygonOffsetUnits = -2; - // } - // }); }); } } diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 8cd3214c..f9f43d50 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -16,7 +16,8 @@ AFRAME.registerComponent('street-generated-striping', { 'short-dashed-stripe-yellow', 'solid-doubleyellow', 'solid-dashed', - 'solid-dashed-yellow' + 'solid-dashed-yellow', + 'solid-dashed-yellow-mirror' ] }, segmentWidth: { @@ -89,16 +90,6 @@ AFRAME.registerComponent('street-generated-striping', { this.el.appendChild(clone); this.createdEntities.push(clone); - // // Fix z fighting issue - // clone.addEventListener('loaded', () => { - // const mesh = clone.getObject3D('mesh'); - // if (mesh) { - // const material = mesh.material; - // material.polygonOffset = true; - // material.polygonOffsetFactor = -2; - // material.polygonOffsetUnits = -2; - // } - // }); }, calculateStripingMaterial: function (stripingName, length) { // calculate the repeatCount for the material From 8976f5ce24a645c250c676ca070842c8f909bd38 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 6 Jan 2025 23:49:41 -0800 Subject: [PATCH 41/53] standard depth buffer not ready for production --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index a03407bc..2be1e2ac 100644 --- a/index.html +++ b/index.html @@ -65,7 +65,7 @@ --> From 320923c1d326f6ea00e7786b79fe4d47a7055078 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 7 Jan 2025 16:46:11 -0800 Subject: [PATCH 42/53] obj.isMesh --- src/components/polygon-offset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/components/polygon-offset.js b/src/components/polygon-offset.js index f715ce01..030cebe9 100644 --- a/src/components/polygon-offset.js +++ b/src/components/polygon-offset.js @@ -24,7 +24,7 @@ AFRAME.registerComponent('polygon-offset', { if (!object3D) return; object3D.traverse((obj) => { - if (obj.type === 'Mesh') { + if (obj.isMesh) { if (Array.isArray(obj.material)) { obj.material.forEach((material) => { this.updateMaterial(material); @@ -59,7 +59,7 @@ AFRAME.registerComponent('polygon-offset', { const mesh = this.el.getObject3D('mesh'); if (mesh) { mesh.traverse((obj) => { - if (obj.type === 'Mesh') { + if (obj.isMesh) { if (Array.isArray(obj.material)) { obj.material.forEach((material) => { material.polygonOffset = false; From 54310b062023f506b3f282a13f7b1e2aac76468e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 7 Jan 2025 16:50:20 -0800 Subject: [PATCH 43/53] remove unused code --- src/components/street-align.js | 42 +--------------------------------- 1 file changed, 1 insertion(+), 41 deletions(-) diff --git a/src/components/street-align.js b/src/components/street-align.js index dd1ada50..d245a2f6 100644 --- a/src/components/street-align.js +++ b/src/components/street-align.js @@ -22,17 +22,12 @@ AFRAME.registerComponent('street-align', { // Initial alignment this.realignStreet(); - // TODO: Still need this for loading from saved scene + // wait for all components, including managed-street to be initialized setTimeout(() => { this.realignStreet(); }, 0); }, - onSegmentWidthChanged: function (event) { - console.log('segment width changed handler called', event); - this.realignStreet(); - }, - update: function (oldData) { const data = this.data; const diff = AFRAME.utils.diff(oldData, data); @@ -48,41 +43,6 @@ AFRAME.registerComponent('street-align', { } }, - setupMutationObserver: function () { - if (this.observer) { - this.observer.disconnect(); - } - - this.observer = new MutationObserver((mutations) => { - let needsReflow = false; - - mutations.forEach((mutation) => { - if (mutation.type === 'childList') { - // Instead of handling segments individually, refresh the entire list - if ( - Array.from(mutation.addedNodes).some( - (node) => node.hasAttribute && node.hasAttribute('street-segment') - ) || - Array.from(mutation.removedNodes).some( - (node) => node.hasAttribute && node.hasAttribute('street-segment') - ) - ) { - needsReflow = true; - } - } - }); - - if (needsReflow) { - this.realignStreet(); - } - }); - - this.observer.observe(this.el, { - childList: true, - subtree: false - }); - }, - realignStreet: function () { const data = this.data; From 2e13d897d420bd6c2d1b5dc45110f7bbd3f88174 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 7 Jan 2025 16:57:44 -0800 Subject: [PATCH 44/53] bind realignStreet --- src/components/street-align.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/street-align.js b/src/components/street-align.js index d245a2f6..220f818a 100644 --- a/src/components/street-align.js +++ b/src/components/street-align.js @@ -17,7 +17,8 @@ AFRAME.registerComponent('street-align', { init: function () { // Listen for any segment changes from managed-street - this.el.addEventListener('segments-changed', () => this.realignStreet()); + this.realignStreet = this.realignStreet.bind(this); + this.el.addEventListener('segments-changed', this.realignStreet); // Initial alignment this.realignStreet(); From 2e90f1d4022579ad861ade7498fa66991d99e929 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 7 Jan 2025 17:04:11 -0800 Subject: [PATCH 45/53] dirt box fixes --- src/components/street-ground.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/components/street-ground.js b/src/components/street-ground.js index 900d213d..d2ec7e90 100644 --- a/src/components/street-ground.js +++ b/src/components/street-ground.js @@ -2,26 +2,21 @@ AFRAME.registerComponent('street-ground', { dependencies: ['managed-street', 'street-align'], init: function () { + this.createOrUpdateDirtbox = this.createOrUpdateDirtbox.bind(this); + // Listen for any changes from managed-street - this.el.addEventListener('segments-changed', () => - this.createOrUpdateDirtbox() - ); + this.el.addEventListener('segments-changed', this.createOrUpdateDirtbox); // Listen for alignment changes - this.el.addEventListener('alignment-changed', () => - this.createOrUpdateDirtbox() - ); + this.el.addEventListener('alignment-changed', this.createOrUpdateDirtbox); // Create initial dirtbox - this.createOrUpdateDirtbox(); - setTimeout(() => { this.createOrUpdateDirtbox(); }, 0); }, createOrUpdateDirtbox: function () { - console.log('dirtbox fired update'); // Find or create dirtbox element if (!this.dirtbox) { this.dirtbox = this.el.querySelector('.dirtbox'); @@ -87,8 +82,12 @@ AFRAME.registerComponent('street-ground', { remove: function () { // Clean up if (this.dirtbox) { - this.dirtbox.parentNode.removeChild(this.dirtbox); + this.dirtbox.remove(); } this.el.removeEventListener('segments-changed', this.createOrUpdateDirtbox); + this.el.removeEventListener( + 'alignment-changed', + this.createOrUpdateDirtbox + ); } }); From b1051b28ad0bd6ba3cdd6cdab3fde3cbbc090219 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 8 Jan 2025 13:00:09 -0800 Subject: [PATCH 46/53] handle null streetlength --- src/components/managed-street.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index eb836bda..c47bea94 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -510,14 +510,15 @@ AFRAME.registerComponent('managed-street', { const streetplanData = await response.json(); const boulevard = streetplanData.project['My Street']['Boulevard Alt 1']; + const streetLength = + parseFloat(streetplanData.project['My Street'].LengthMiles) * + 5280 * + 0.3048 || 100; // Convert miles to meters // Convert StreetPlan format to managed-street format const streetObject = { name: streetplanData.project.ProjectName, width: 0, // Will be calculated from segments - length: - parseFloat(streetplanData.project['My Street'].LengthMiles) * - 5280 * - 0.3048, // Convert miles to meters + length: streetLength, segments: [] }; From de662b825672b4decb4ff5200e45f27022f966a2 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 9 Jan 2025 09:14:46 -0800 Subject: [PATCH 47/53] street-label cleanup fix binding I have to keep this in or it raises an error `Cannot read properties of undefined (reading 'width')`. My guess is there is a race condition such that when the scene is loaded the dom attribute is added but the a-frame component hasn't been initialized thus the segment is returned as matching the querySelector but not from a-frame getattribute? I changed the logic a bit to check if the street-segment component exist and to also return if no width for a segment: ``` const segmentWidth = segmentEl.getAttribute('street-segment')?.width; if (!segmentWidth) return; ``` --- src/components/street-label.js | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/components/street-label.js b/src/components/street-label.js index 66b9ba72..8ce21127 100644 --- a/src/components/street-label.js +++ b/src/components/street-label.js @@ -20,14 +20,10 @@ AFRAME.registerComponent('street-label', { // Create and setup canvas this.createAndSetupCanvas(); - // Listen for segment changes - this.el.addEventListener('segments-changed', this.updateLabels.bind(this)); - - // Listen for alignment changes - this.el.addEventListener('alignment-changed', this.updateLabels.bind(this)); - - // Initial update - this.updateLabels(); + // Listen for segment & alignment changes + this.updateLabels = this.updateLabels.bind(this); + this.el.addEventListener('segments-changed', this.updateLabels); + this.el.addEventListener('alignment-changed', this.updateLabels); // Handle loading from saved scene setTimeout(() => { @@ -64,9 +60,9 @@ AFRAME.registerComponent('street-label', { const labelsArray = []; segments.forEach((segmentEl) => { - if (!segmentEl.getAttribute('street-segment')) return; + const segmentWidth = segmentEl.getAttribute('street-segment')?.width; + if (!segmentWidth) return; - const segmentWidth = segmentEl.getAttribute('street-segment').width; widthsArray.push(segmentWidth); labelsArray.push(segmentEl.getAttribute('data-layer-name') || ''); }); From 1604642c86d0c446fda6c86ca26a539b0b7dd3a8 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 9 Jan 2025 09:17:29 -0800 Subject: [PATCH 48/53] more cleanups --- src/components/street-align.js | 3 --- src/editor/components/components/Sidebar.js | 1 - 2 files changed, 4 deletions(-) diff --git a/src/components/street-align.js b/src/components/street-align.js index 220f818a..4a214a5e 100644 --- a/src/components/street-align.js +++ b/src/components/street-align.js @@ -20,9 +20,6 @@ AFRAME.registerComponent('street-align', { this.realignStreet = this.realignStreet.bind(this); this.el.addEventListener('segments-changed', this.realignStreet); - // Initial alignment - this.realignStreet(); - // wait for all components, including managed-street to be initialized setTimeout(() => { this.realignStreet(); diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index ccb692d8..817a1eeb 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -46,7 +46,6 @@ export default class Sidebar extends React.Component { : 'Unknown'; }; - // entity.getAttribute('data-parent-component') fireParentComponentDetach = (entity) => { const componentName = entity.getAttribute('data-parent-component'); const parentEntity = entity.parentElement; From fbd2ecb782b43998ea1ae10aae41a088985c4c8e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 9 Jan 2025 09:20:44 -0800 Subject: [PATCH 49/53] clean up binding --- src/components/managed-street.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index fc680012..4e7c89b4 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -51,6 +51,8 @@ AFRAME.registerComponent('managed-street', { this.actualWidth = 0; // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); + this.onSegmentWidthChanged = this.onSegmentWidthChanged.bind(this); + if (this.data.enableAlignment && !this.el.hasAttribute('street-align')) { this.el.setAttribute('street-align', ''); } @@ -73,7 +75,7 @@ AFRAME.registerComponent('managed-street', { console.log('Attaching width change listener to existing segment'); segment.addEventListener( 'segment-width-changed', - this.onSegmentWidthChanged.bind(this) + this.onSegmentWidthChanged ); }); }, @@ -174,7 +176,7 @@ AFRAME.registerComponent('managed-street', { addedSegments.forEach((segment) => { segment.addEventListener( 'segment-width-changed', - this.onSegmentWidthChanged.bind(this) + this.onSegmentWidthChanged ); }); @@ -182,7 +184,7 @@ AFRAME.registerComponent('managed-street', { removedSegments.forEach((segment) => { segment.removeEventListener( 'segment-width-changed', - this.onSegmentWidthChanged.bind(this) + this.onSegmentWidthChanged ); }); From 4f21775fa625356fd12dafde1034672dec12db44 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 9 Jan 2025 15:49:27 -0800 Subject: [PATCH 50/53] add surface color to streetplan mapping --- src/components/managed-street.js | 72 ++++++++++++++++---------------- 1 file changed, 36 insertions(+), 36 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index c47bea94..5bf90726 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -8,36 +8,36 @@ const streetmixParsersTested = require('../tested/aframe-streetmix-parsers-teste // STREETPLAN HELPER FUNCTIONS // Material mapping from Streetplan to 3DStreet surfaces const STREETPLAN_MATERIAL_MAPPING = { - 'asphalt black': 'asphalt', - 'asphalt blue': 'asphalt', - 'asphalt red 1': 'asphalt', - 'asphalt red 2': 'asphalt', - 'asphalt green': 'asphalt', - 'asphalt old': 'asphalt', - 'standard concrete': 'concrete', - grass: 'grass', - 'grass dead': 'grass', - 'pavers tan': 'sidewalk', - 'pavers brown': 'sidewalk', - 'pavers mixed': 'sidewalk', - 'pavers red': 'sidewalk', - 'tint conc. or dirt': 'gravel', - dirt: 'gravel', - gravel: 'gravel', - stonetan: 'sidewalk', - 'sidewalk 2': 'sidewalk', - 'cobble stone': 'sidewalk', - 'solid black': 'solid', - 'painted intersection': 'asphalt', - 'grass with edging': 'grass', - xeriscape: 'grass', - 'grassslopemedian 12ft': 'grass', - 'grassslopemedian 24ft': 'grass', - 'grassslope 12ft-left': 'grass', - 'grassslope 12ft-right': 'grass', - 'grassslope 24ft-left': 'grass', - 'grassslope 24ft-right': 'grass', - sand: 'sand' + 'asphalt black': { surface: 'asphalt', color: '#aaaaaa' }, + 'asphalt blue': { surface: 'asphalt', color: '#aaaaff' }, + 'asphalt red 1': { surface: 'asphalt', color: '#ffaaaa' }, + 'asphalt red 2': { surface: 'asphalt', color: '#ff0000' }, + 'asphalt green': { surface: 'asphalt', color: '#aaffaa' }, + 'asphalt old': { surface: 'asphalt' }, + 'standard concrete': { surface: 'concrete' }, + grass: { surface: 'grass' }, + 'grass dead': { surface: 'grass' }, + 'pavers tan': { surface: 'sidewalk' }, + 'pavers brown': { surface: 'sidewalk' }, + 'pavers mixed': { surface: 'sidewalk' }, + 'pavers red': { surface: 'sidewalk', color: '#ffaaaa' }, + 'tint conc. or dirt': { surface: 'gravel' }, + dirt: { surface: 'gravel' }, + gravel: { surface: 'gravel' }, + stonetan: { surface: 'sidewalk' }, + 'sidewalk 2': { surface: 'sidewalk' }, + 'cobble stone': { surface: 'sidewalk' }, + 'solid black': { surface: 'solid' }, + 'painted intersection': { surface: 'asphalt' }, + 'grass with edging': { surface: 'grass' }, + xeriscape: { surface: 'grass' }, + 'grassslopemedian 12ft': { surface: 'grass' }, + 'grassslopemedian 24ft': { surface: 'grass' }, + 'grassslope 12ft-left': { surface: 'grass' }, + 'grassslope 12ft-right': { surface: 'grass' }, + 'grassslope 24ft-left': { surface: 'grass' }, + 'grassslope 24ft-right': { surface: 'grass' }, + sand: { surface: 'sand' } }; const STREETPLAN_OBJECT_MAPPING = { @@ -522,7 +522,7 @@ AFRAME.registerComponent('managed-street', { segments: [] }; - // Process segments + // Process streetplan segments const segments = boulevard.segments; for (const segmentKey in segments) { const segment = segments[segmentKey]; @@ -535,11 +535,11 @@ AFRAME.registerComponent('managed-street', { const segmentWidth = parseFloat(segment.width) * 0.3048; // Convert feet to meters streetObject.width += segmentWidth; - // Convert segment type based on your schema + // Convert streetplan segment type based on your schema let segmentType = 'drive-lane'; // Default type let segmentDirection = 'inbound'; - let segmentColor = window.STREET.colors.white; + // convert from streetplan type to managed street default type switch (segment.Type) { case 'BikesPaths': segmentType = 'bike-lane'; @@ -549,7 +549,6 @@ AFRAME.registerComponent('managed-street', { break; case 'Transit': segmentType = 'bus-lane'; - segmentColor = window.STREET.colors.red; break; case 'Median/Buffer': segmentType = 'divider'; @@ -576,7 +575,8 @@ AFRAME.registerComponent('managed-street', { // Map the material using the STREETPLAN_MATERIAL_MAPPING, fallback to 'asphalt' if not found const material = segment.Material?.toLowerCase() || ''; const mappedSurface = - STREETPLAN_MATERIAL_MAPPING[material] || 'asphalt'; + STREETPLAN_MATERIAL_MAPPING[material]?.surface || 'asphalt'; + const mappedColor = STREETPLAN_MATERIAL_MAPPING[material]?.color; // Map the O-Tags to clone configurations const generated = {}; @@ -600,7 +600,7 @@ AFRAME.registerComponent('managed-street', { name: segment.title, level: parseFloat(segment.MaterialH) || 0, direction: segmentDirection, - color: segmentColor, + color: mappedColor || window.STREET.types[segmentType]?.color, surface: mappedSurface, generated: clones.length > 0 ? generated : undefined }); From 0fb5a85bc3e31f98af94c2b52f51c28e665fe412 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 9 Jan 2025 15:57:16 -0800 Subject: [PATCH 51/53] map MaterialH > level --- src/components/managed-street.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 5bf90726..53d6e5d6 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -598,7 +598,7 @@ AFRAME.registerComponent('managed-street', { type: segmentType, width: segmentWidth, name: segment.title, - level: parseFloat(segment.MaterialH) || 0, + level: parseFloat(segment.MaterialH) === 0.5 ? 1 : 0, direction: segmentDirection, color: mappedColor || window.STREET.types[segmentType]?.color, surface: mappedSurface, From 7b24be5ae518f7f8956b9646ae28badedef4136e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 9 Jan 2025 16:51:12 -0800 Subject: [PATCH 52/53] add additional streetplan mappings --- src/components/managed-street.js | 263 +++++++++++++++++-------------- 1 file changed, 145 insertions(+), 118 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 53d6e5d6..ea301d90 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -41,134 +41,161 @@ const STREETPLAN_MATERIAL_MAPPING = { }; const STREETPLAN_OBJECT_MAPPING = { - 'Away, Left Park, Head In': '', - 'Barrier 1-ft': '', - 'Barrier 2-ft': '', - 'Bike Food Cart': '', - 'BikeRack Bollard': '', - 'Bikelane ShareCar': '', - 'Blank PedRefuge (8ft)': '', - 'Blue Car': '', - 'Blue Mailbox': '', - 'Bollard Plastic Yellow': '', - 'Boxwood planter 2ft': '', - 'Boxwood planter 3ft': '', - 'Boxwood planter 5ft': '', - 'Bur Oak': 'tree3', - 'Cactus Median (10ft)': '', - 'Cactus Median (12ft)': '', - 'Cactus Median (4ft)': '', - 'Cactus Median (6ft)': '', - 'Cactus Median (8ft)': '', - 'DesertWillow Texas': 'tree3', - 'Empty place holder': '', - 'English oak': 'tree3', - 'FleaMarket Stuff': '', - 'Flower Median (10ft)': '', - 'Flower Median (12ft)': '', - 'Flower Median (4ft)': '', - 'Flower Median (6ft)': '', - 'Flower Median (8ft)': '', - 'Flower Pot 4ft': '', - 'FloweringPear 18ft': '', - 'Flowers PedRefuge (8ft)': '', - Goldenraintree: 'tree3', - 'GrassMound (10ft)': '', - 'GrassMound (12ft)': '', - 'GrassMound (4ft)': '', - 'GrassMound (6ft)': '', - 'GrassMound (8ft)': '', - 'Grassy Median (10ft)': '', - 'Grassy Median (12ft)': '', - 'Grassy Median (4ft)': '', - 'Grassy Median (6ft)': '', - 'Grassy Median (8ft)': '', - 'Green Car': '', - 'Historic Light': '', - Honeylocust: '', - 'Japanese Zelkova': 'tree3', - 'Japanese lilac': 'tree3', - 'Jerusalem Thorn': 'tree3', - 'Kentucky Coffeetree': 'tree3', - 'Large Food Cart': '', - 'Large Oak': '', - 'Light rail poles': '', - 'Moto highway rider': '', - 'Mountable Barrier 1-ft': '', - 'NYC Bike Rack': '', - 'Orange Barrel': '', - 'Palm Tree': 'palm-tree', - 'PalmTree 20ft': 'palm-tree', - 'PalmTree 28ft': 'palm-tree', - 'Pine Tree': 'tree3', - 'Pink flower 16ft': '', - 'Planter flowers': '', - 'Planter with bench': '', - 'Power Tower 30ft': '', - 'Purpendicular Right side, Blue': '', - 'Purpendicular Right side, Red': '', - 'Purpleleaf plum': '', - 'Red berries 14ft': '', - 'Rock Median (10ft)': '', - 'Rock Median (12ft)': '', - 'Rock Median (4ft)': '', - 'Rock Median (6ft)': '', - 'Rock Median (8ft)': '', - 'Semi Truck': '', - Shelter: '', - 'Shelter Roundroof': '', - 'Sign directory': '', - 'Small Tree': 'tree3', - 'SmartCar 5ft': '', - 'SoundWall (12ft)': '', - 'SoundWall (8ft)': '', - 'SoundWall Plants (12ft)': '', - 'SoundWall Plants (8ft)': '', - 'Street light': '', - 'Streetlight solar': '', - 'Streetlight solar banners 1': '', - 'Streetlight solar banners 2': '', - TallGrass: '', - 'TallPlantBox (10ft)': '', - 'TallPlantBox (12ft)': 'dividers-bush', - 'TallPlantBox (4ft)': '', - 'TallPlantBox (6ft)': '', - 'TallPlantBox (8ft)': '', - 'TallPlantBox PedRef (10ft)': '', - 'TallPlantBox PedRef (12ft)': '', - 'TallPlantBox PedRef (6ft)': '', - 'TallPlantBox PedRef (8ft)': '', - 'Telephone pole': '', - 'Tent BlueWhite': '', - 'Tent Veggie': '', - 'Toward, Right Park, Head In': '', - 'Tropical Median (4ft)': '', - 'Weeds Median (4ft)': '', - 'Weeds Median (6ft)': '', - 'Weeds Median (8ft)': '', - 'White Sedan': '', - 'White Truck': '', - 'White coup': '', - 'Yellow Sedan': '', - 'historic no banner': '', - 'historic with banners': '', - 'historic with flowers 1': '', - 'historic with flowers 2': '', - 'random trashcan': '', - trashcan: '' + 'away, left park, head in': '', + 'barrier 1-ft': 'temporary-jersey-barrier-concrete', + 'barrier 2-ft': 'temporary-jersey-barrier-concrete', + 'bike food cart': '', + 'bikelane sharecar': '', + 'bikerack bollard': '', + 'blank pedrefuge (8ft)': '', + 'blue car': 'sedan-rig', + 'blue mailbox': 'usps-mailbox', + 'bollard plastic yellow': 'bollard', + boulevardcirculator: 'minibus', + 'boulevardcirculator rev': 'minibus', + 'boxwood planter 2ft': 'dividers-planter-box', + 'boxwood planter 3ft': 'dividers-planter-box', + 'boxwood planter 5ft': 'dividers-planter-box', + 'bur oak': 'tree3', + bus: 'bus', + 'bus rev': 'bus', + 'cactus median (10ft)': 'dividers-bush', + 'cactus median (12ft)': 'dividers-bush', + 'cactus median (4ft)': 'dividers-bush', + 'cactus median (6ft)': 'dividers-bush', + 'cactus median (8ft)': 'dividers-bush', + 'casual woman': '', + couple: '', + 'couple biking': '', + 'desertwillow texas': 'tree3', + 'dog walker': '', + 'empty place holder': '', + 'english oak': 'tree3', + 'fleamarket stuff': '', + 'flower median (10ft)': 'dividers-flowers', + 'flower median (12ft)': 'dividers-flowers', + 'flower median (4ft)': 'dividers-flowers', + 'flower median (6ft)': 'dividers-flowers', + 'flower median (8ft)': 'dividers-flowers', + 'flower pot 4ft': 'dividers-flowers', + 'floweringpear 18ft': 'tree3', + 'flowers pedrefuge (8ft)': 'dividers-flowers', + goldenraintree: 'tree3', + 'golfcart red 4ft back': 'tuk-tuk', + 'grassmound (10ft)': '', + 'grassmound (12ft)': '', + 'grassmound (4ft)': '', + 'grassmound (6ft)': '', + 'grassmound (8ft)': '', + 'grassy median (10ft)': '', + 'grassy median (12ft)': '', + 'grassy median (4ft)': '', + 'grassy median (6ft)': '', + 'grassy median (8ft)': '', + 'green car': 'sedan-rig', + 'heavy rail': 'tram', + 'heavy rail rev': 'tram', + 'historic light': 'lamp-traditional', + 'historic no banner': 'lamp-traditional', + 'historic with banners': 'lamp-traditional', + 'historic with flowers 1': 'lamp-traditional', + 'historic with flowers 2': 'lamp-traditional', + honeylocust: 'tree3', + 'japanese lilac': 'tree3', + 'japanese zelkova': 'tree3', + 'jerusalem thorn': 'tree3', + 'kentucky coffeetree': 'tree3', + 'large food cart': '', + 'large oak': 'tree3', + 'light rail poles': '', + 'moto highway rider': 'motorbike', + 'mountable barrier 1-ft': '', + 'nev shuttle back': 'minibus', + 'nev shuttle front': 'minibus', + 'nyc bike rack': 'bikerack', + 'orange barrel': 'temporary-traffic-cone', + 'palm tree': 'palm-tree', + 'palmtree 20ft': 'palm-tree', + 'palmtree 28ft': 'palm-tree', + 'pine tree': 'tree3', + 'pink flower 16ft': 'tree3', + 'planter flowers': 'dividers-flowers', + 'planter with bench': 'bench', + 'polaris gem e4': 'tuk-tuk', + 'power tower 30ft': '', + 'purpendicular right side, blue': '', + 'purpendicular right side, red': '', + 'purpleleaf plum': 'tree3', + 'random trashcan': 'trash-bin', + 'red berries 14ft': 'tree3', + 'red car': 'sedan-rig', + 'red jeep': 'suv-rig', + 'rock median (10ft)': '', + 'rock median (12ft)': '', + 'rock median (4ft)': '', + 'rock median (6ft)': '', + 'rock median (8ft)': '', + 'semi truck': 'box-truck-rig', + 'serious man': '', + shelter: 'bus-stop', + 'shelter roundroof': 'bus-stop', + 'sign directory': 'wayfinding', + 'silver suv': 'suv-rig', + 'small tree': 'tree3', + smallnev: 'minibus', + 'smartcar 5ft': 'self-driving-cruise-car-rig', + 'soundwall (12ft)': '', + 'soundwall (8ft)': '', + 'soundwall plants (12ft)': '', + 'soundwall plants (8ft)': '', + 'street light': 'lamp-modern', + 'streetcar blue': 'trolley', + 'streetcar red 1': 'trolley', + 'streetcar red 2': 'trolley', + 'streetcar yellow': 'trolley', + 'streetlight solar': 'lamp-modern', + 'streetlight solar banners 1': 'lamp-modern', + 'streetlight solar banners 2': 'lamp-modern', + tallgrass: '', + 'tallplantbox (10ft)': '', + 'tallplantbox (12ft)': 'dividers-bush', + 'tallplantbox (4ft)': '', + 'tallplantbox (6ft)': '', + 'tallplantbox (8ft)': '', + 'tallplantbox pedref (10ft)': '', + 'tallplantbox pedref (12ft)': '', + 'tallplantbox pedref (6ft)': '', + 'tallplantbox pedref (8ft)': '', + 'telephone pole': 'utility_pole', + 'tent bluewhite': '', + 'tent veggie': '', + 'toward, right park, head in': '', + trashcan: 'trash-bin', + 'tropical median (4ft)': 'palm-tree', + 'two bikes back': '', + 'uta bus': 'bus', + 'uta lightrail': 'tram', + 'uta lightrail rev': 'tram', + 'weeds median (4ft)': '', + 'weeds median (6ft)': '', + 'weeds median (8ft)': '', + 'white coup': 'sedan-rig', + 'white sedan': 'sedan-rig', + 'white truck': 'box-truck-rig', + 'yellow sedan': 'sedan-rig' }; -// Helper function to parse O-Tags string into array +// Streetplan Helper function to parse O-Tags string into array function parseOTags(tags) { if (!tags || tags === '-') return []; return tags.split('", "').map((t) => t.replace(/"/g, '').trim()); } -// Helper function to create clone configuration +// Streetplan Helper function to create clone configuration function createCloneConfig(name, tags) { if (!name || name === '-') return null; - const model = STREETPLAN_OBJECT_MAPPING[name]; + const model = STREETPLAN_OBJECT_MAPPING[name.toLowerCase()]; if (!model) return null; return { From 506474b769d5981b8a16677cb6b074cad2c6d6b8 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 13 Jan 2025 14:17:37 -0800 Subject: [PATCH 53/53] use new streetplan loader by default remove old streetplan loader --- src/components/streetplan-loader.js | 140 ----------- src/index.js | 1 - src/json-utils_1.1.js | 27 ++- src/streetplan/conversion-map.js | 347 ---------------------------- src/streetplan/streetplan-utils.js | 87 ------- 5 files changed, 20 insertions(+), 582 deletions(-) delete mode 100644 src/components/streetplan-loader.js delete mode 100644 src/streetplan/conversion-map.js delete mode 100644 src/streetplan/streetplan-utils.js diff --git a/src/components/streetplan-loader.js b/src/components/streetplan-loader.js deleted file mode 100644 index 8954d344..00000000 --- a/src/components/streetplan-loader.js +++ /dev/null @@ -1,140 +0,0 @@ -/* global AFRAME, XMLHttpRequest */ -import useStore from '../store.js'; -var streetplanUtils = require('../streetplan/streetplan-utils.js'); - -const state = useStore.getState(); - -AFRAME.registerComponent('streetplan-loader', { - dependencies: ['street'], - schema: { - streetplanStreetURL: { type: 'string' }, - streetplanAPIURL: { type: 'string' }, - streetplanEncJSON: { type: 'string' }, - showBuildings: { default: true }, - name: { default: '' }, - synchronize: { default: false } - }, - streetplanResponseParse: function (streetplanResponseObject) { - const el = this.el; - const data = this.data; - if (!streetplanResponseObject || !streetplanResponseObject.project) { - console.error('[streetplan-loader] Invalid streetplan data structure'); - return; - } - try { - // convert Streetplan structure to Streetmix-like structure - const streetData = streetplanUtils.convertStreetStruct( - streetplanResponseObject - ); - - const streetplanSegments = streetData.segments; - const streetplanName = streetData.name; - // const projectName = streetData.projectName || streetplanName; - - // Update layer name with project and street names - el.setAttribute('data-layer-name', `StreetPlan • ${streetplanName}`); - - if (!state.sceneTitle) { - state.setSceneTitle(streetplanName); - } - - // Handle buildings if enabled - if (data.showBuildings) { - // Find building segments in the full data - const buildingSegments = streetplanSegments.filter( - (segment) => segment.type === 'Buildings' - ); - - // Set building variants based on side - const leftBuilding = buildingSegments.find((b) => b.side === 'left'); - const rightBuilding = buildingSegments.find((b) => b.side === 'right'); - - if (leftBuilding) { - el.setAttribute('street', 'left', leftBuilding.title); - } - if (rightBuilding) { - el.setAttribute('street', 'right', rightBuilding.title); - } - } - // Set street type - el.setAttribute('street', 'type', 'streetmixSegmentsMetric'); - - // Filter out building segments for the main street data if needed - const finalSegments = data.showBuildings - ? streetplanSegments.filter((s) => s.type !== 'Buildings') - : streetplanSegments; - - // Set JSON attribute last - el.setAttribute( - 'street', - 'JSON', - JSON.stringify({ streetmixSegmentsMetric: finalSegments }) - ); - el.emit('streetplan-loader-street-loaded'); - } catch (error) { - console.error('[streetplan-loader] Error parsing street data:', error); - el.emit('streetplan-loader-error', { error }); - } - }, - init: function () { - this.el.setAttribute('streetplan-loader', 'synchronize', true); - }, - update: function (oldData) { - const data = this.data; - - // Skip update if synchronization is disabled - if (!data.synchronize) return; - - // Handle URL encoded JSON data - if (data.streetplanEncJSON) { - try { - const streetplanJSON = decodeURIComponent(data.streetplanEncJSON); - this.streetplanResponseParse(JSON.parse(streetplanJSON)); - } catch (error) { - console.error('[streetplan-loader] Error parsing encoded JSON:', error); - this.el.emit('streetplan-loader-error', { error }); - } - return; - } - - // Skip if URLs haven't changed - if ( - oldData.streetplanStreetURL === data.streetplanStreetURL && - oldData.streetplanAPIURL === data.streetplanAPIURL - ) { - return; - } - - // Load from API - const request = new XMLHttpRequest(); - console.log('[streetplan-loader]', 'GET ' + data.streetplanAPIURL); - - request.open('GET', data.streetplanAPIURL, true); - request.onload = () => { - if (request.status >= 200 && request.status < 400) { - try { - const streetplanResponseObject = JSON.parse(request.response); - this.streetplanResponseParse(streetplanResponseObject); - this.el.setAttribute('streetplan-loader', 'synchronize', false); - } catch (error) { - console.error( - '[streetplan-loader] Error parsing API response:', - error - ); - this.el.emit('streetplan-loader-error', { error }); - } - } else { - const error = new Error(`Server returned status ${request.status}`); - console.error('[streetplan-loader] API request failed:', error); - this.el.emit('streetplan-loader-error', { error }); - } - }; - - request.onerror = () => { - const error = new Error('Network request failed'); - console.error('[streetplan-loader] Network error:', error); - this.el.emit('streetplan-loader-error', { error }); - }; - request.send(); - } -}); diff --git a/src/index.js b/src/index.js index b223b2ad..ae18771a 100644 --- a/src/index.js +++ b/src/index.js @@ -16,7 +16,6 @@ require('./components/notify.js'); require('./components/create-from-json'); require('./components/screentock.js'); require('aframe-atlas-uvs-component'); -require('./components/streetplan-loader'); require('./components/street-geo.js'); require('./components/street-environment.js'); require('./components/intersection.js'); diff --git a/src/json-utils_1.1.js b/src/json-utils_1.1.js index dac6e2cb..91bb719c 100644 --- a/src/json-utils_1.1.js +++ b/src/json-utils_1.1.js @@ -1,4 +1,5 @@ import useStore from './store'; +import { createUniqueId } from './editor/lib/entity'; /* global AFRAME, Node */ /* version: 1.0 */ @@ -529,18 +530,30 @@ AFRAME.registerComponent('set-loader-from-hash', { streetURL ); } else if (streetURL.includes('streetplan.net/')) { - // load from Streetplan encoded JSON in URL + // instead, load streetplan via managed street the new addlayerpanel console.log( '[set-loader-from-hash]', - 'Set streetplan-loader streetplanAPIURL to', + 'Create new Managed Street with StreetPlan URL', streetURL ); + if (streetURL && streetURL !== '') { + const definition = { + id: createUniqueId(), + components: { + 'managed-street': { + sourceType: 'streetplan-url', + sourceValue: streetURL, + showVehicles: true, + showStriping: true, + synchronize: true + } + } + }; - this.el.setAttribute( - 'streetplan-loader', - 'streetplanAPIURL', - streetURL - ); + setTimeout(() => { + AFRAME.INSPECTOR.execute('entitycreate', definition); + }, 1000); + } } else { // try to load JSON file from remote resource console.log( diff --git a/src/streetplan/conversion-map.js b/src/streetplan/conversion-map.js deleted file mode 100644 index 82db4ded..00000000 --- a/src/streetplan/conversion-map.js +++ /dev/null @@ -1,347 +0,0 @@ -// conversion map StreetPan -> Streetmix sidewalk segment types mapping -/* -StreetPlanType1: - { - StreetPlanSubtype: StreetmixType, - --- or --- - StreetPlanSubtype: { - "tag": StreetPlanTag, - "type": StreetmixType, - "variantString": Streetmix VariantString, can be formed based on other Streetplan parameters - (Name or Tag) or be constant, like: 'sidewalk', - - "variantStringAdd": get parameter values from this list and generate variantString. - Often variantString looks like this: 'outbound|regular|road' - example for bike-path. - variantStringAdd will be: 'direction|material|variantString', - - "nameToVariantMap": mapping rule StreetPlan O1-Name -> VariantString, - "tagToVariantMap": mapping rule StreetPlan O1-Tags -> VariantString, - "names": names (StreetPlan O1-Name) for this Streetmix Segment type - }, - --- or --- - // for one (O1-Tags) there can be different streetmix segment types, - // which are determined by the name (O1-Name) - StreetPlanSubtype: [ - different options of tags (O1-Tags) and streetMix data for each - ] - } -*/ -const mapping = { - Setback: { - '': { type: 'sidewalk', variantString: 'empty' }, - Trees: { type: 'sidewalk-tree', variantString: 'big' }, - tree: { type: 'divider', variantString: 'palm-tree' }, - Benchs: { type: 'sidewalk-bench', variantStringAdd: 'side' } - }, - Walkways: { - '': { type: 'sidewalk', variantString: 'empty' }, - Trees: { type: 'sidewalk-tree', variantString: 'big' }, - pedestrian: { type: 'sidewalk', variantString: 'dense' }, - Benchs: { type: 'sidewalk-bench', variantStringAdd: 'side' }, - Tables: { type: 'outdoor-dining', variantString: 'occupied|sidewalk' } - }, - Furniture: { - '': { type: 'sidewalk', variantString: 'empty' }, - Trees: { type: 'sidewalk-tree', variantString: 'big' }, - season_tree: { type: 'sidewalk-tree', variantString: 'big' }, - Shelters: { - type: 'transit-shelter', - variantString: 'street-level', - variantStringAdd: 'side|variantString' - }, - Pedestrian: { type: 'sidewalk', variantString: 'dense' } - }, - Curbside: { - '': { type: 'sidewalk', variantString: 'empty' }, - Lights: { - type: 'sidewalk-lamp', - tagToVariantMap: { - 'Historic Lights': 'traditional', - 'Regular Lights': 'modern' - }, - variantStringAdd: 'side|variantString' - }, - Poles: { type: 'utilities', variantStringAdd: 'side' }, - BikeRacks: { - type: 'sidewalk-bike-rack', - nameToVariantMap: { - 'Sideview Modern': 'sidewalk-parallel', - Sideview: 'sidewalk-parallel', - 'NYC Bike Rack': 'sidewalk' - }, - variantStringAdd: 'side|variantString' - } - }, - BikesPaths: { - '': { type: 'bike-lane', variantString: 'sidewalk' }, - Bikes: { - type: 'bike-lane', - variantString: 'sidewalk', - variantStringAdd: 'direction|material|variantString' - } - }, - Gutter: { - '': { type: 'divider', variantString: 'median' }, - Gutter: { type: 'divider', variantString: 'median' } - }, - Transit: { - '': { - tag: 'Bus Vehicles', - type: 'bus-lane', - variantString: 'typical', - variantStringAdd: 'direction|material|variantString' - }, - Transit: [ - { - tag: 'Rail Vehicles', - type: 'streetcar', - names: [ - 'StreetCar Yellow', - 'StreetCar Blue', - 'StreetCar Red 1', - 'StreetCar Red 2' - ], - variantStringAdd: 'direction|material' - }, - { - tag: 'Rail Vehicles', - type: 'light-rail', - names: ['UTA LightRail'], - variantStringAdd: 'direction|material' - }, - // there are only reversed light rail vehicles in Streetplan - { - tag: 'Rail Vehicles Reversed', - type: 'light-rail', - variantStringAdd: 'direction|material' - }, - { - tag: 'Bus Vehicles', - type: 'bus-lane', - variantString: 'typical', - variantStringAdd: 'direction|material|variantString' - } - ] - }, - Cars: { - '': { - type: 'drive-lane', - variantString: 'car', - variantStringAdd: 'direction|variantString' - }, - Autos: { - type: 'drive-lane', - variantString: 'car', - variantStringAdd: 'direction|variantString' - }, - Truck: { - type: 'drive-lane', - variantString: 'truck', - variantStringAdd: 'direction|variantString' - } - }, - Parking: { - '': { - tag: 'Parking - Parallel', - type: 'parking-lane', - variantStringAdd: 'direction|side' - }, - Parallel: { - tag: 'Parking - Parallel', - type: 'parking-lane', - variantStringAdd: 'direction|side' - }, - AngleNormal: { - tag: 'Parking - Angle', - type: 'parking-lane', - nameToVariantMap: { - 'Away, L. Park, Head In': 'angled-rear-left', - 'Toward, R. Park, Head In': 'angled-front-right', - 'Toward, L. Park, Head In': 'angled-front-left', - 'Away, R. Park, Head In': 'angled-rear-right' - }, - variantStringAdd: 'side' - }, - Perpendicular: { - type: 'parking-lane', - variantString: 'sideways', - variantStringAdd: 'variantString|side' - } - }, - Buffers: { - '': { type: 'divider', variantString: 'median' }, - Trees: { type: 'divider', variantString: 'big-tree' }, - tree: { type: 'divider', variantString: 'palm-tree' }, - season_tree: { type: 'divider', variantString: 'big-tree' }, - median: { type: 'divider', variantString: 'planting-strip' }, - planter: { type: 'divider', variantString: 'planting-strip' } - } -}; -// copy repeating rules -mapping['Buffers']['AngleNormal'] = mapping['Parking']['AngleNormal']; -mapping['Buffers']['Autos'] = mapping['Cars']['Autos']; -mapping['Buffers']['Purpendicular'] = mapping['Parking']['Perpendicular']; -mapping['Median/Buffer'] = mapping['Buffers']; -mapping['Setback']['tree'] = mapping['Buffers']['tree']; -mapping['Setback']['Trees'] = mapping['Buffers']['Trees']; -mapping['Setback']['season_tree'] = mapping['Buffers']['season_tree']; -// fix for typo Purpendicular -mapping['Parking']['Purpendicular'] = mapping['Parking']['Perpendicular']; -mapping['Setback']['Purpendicular'] = mapping['Parking']['Perpendicular']; -mapping['Setback']['AngleNormal'] = mapping['Parking']['AngleNormal']; -mapping['Setback']['planter'] = mapping['Buffers']['planter']; -mapping['Setback']['BikeRacks'] = mapping['Curbside']['BikeRacks']; -mapping['Setback']['Tables'] = mapping['Walkways']['Tables']; -mapping['Setback']['Poles'] = mapping['Curbside']['Poles']; - -mapping['Curbside']['Shelters'] = mapping['Furniture']['Shelters']; -mapping['Curbside']['Benchs'] = mapping['Walkways']['Benchs']; - -mapping['Furniture']['planter'] = mapping['Buffers']['planter']; -mapping['Furniture']['Benchs'] = mapping['Walkways']['Benchs']; -mapping['Furniture']['BikeRacks'] = mapping['Curbside']['BikeRacks']; -mapping['Furniture']['Tables'] = mapping['Walkways']['Tables']; - -const directionMap = { - Coming: 'inbound', - Going: 'outbound', - // make default outbound direction for both variant - Both: 'both', - NA: '' -}; - -const materialMap = { - 'Asphalt Black': 'regular', - 'Asphalt Blue': 'blue', - 'Asphalt Red 1': 'red', - 'Asphalt Red 2': 'red', - 'Asphalt Green': 'green', - 'Asphalt Old': 'regular', - Grass: 'grass', - 'Grass Dead': 'grass' -}; - -// StreetMix variantString often has additional parameters via |, for example: taxi|outbound|right -// generate a streetMix like variantString from the listed parameters in variantStringAdd -function generateVariantString(variantStringKeys, streetmixData) { - const variantString = variantStringKeys - .split('|') - .map((currKey) => streetmixData[currKey]) - .join('|'); - return variantString; -} - -function getDataFromSubtypeMap(convertRule, streetmixData, streetplanData) { - if (typeof convertRule === 'string') { - // convertRule is a Streetmix type. - // Later will add another options for this case - streetmixData['type'] = convertRule; - } else if (Array.isArray(convertRule)) { - // in this case, different segment subtype options - // are associated with the different Streetmix types - - // find the desired Streetmix segment data from the array by Streetplan tag and names(?) - const variantData = convertRule.find((element) => { - const tagValMatches = element['tag'] === streetplanData['O1-Tags']; - if (tagValMatches && element['names']) { - return element['names'].includes(streetplanData['O1-Name']); - } - return tagValMatches; - }); - - streetmixData['variantString'] = ''; - - const variantString = variantData['variantString']; - if (variantString && typeof variantString === 'string') { - streetmixData['variantString'] = variantString; - } - - // generate a streetMix like variantString from the listed parameter values - streetmixData['type'] = variantData['type']; - const variantStringKeys = variantData['variantStringAdd']; - if (variantStringKeys) { - streetmixData['variantString'] = generateVariantString( - variantStringKeys, - streetmixData - ); - } - } else if (typeof convertRule === 'object') { - // in this case, different variants of the segment subtype - // are associated with different variantString of the Streetmix segment - - streetmixData['type'] = convertRule['type']; - streetmixData['variantString'] = ''; - - const variantString = convertRule['variantString']; - if (variantString && typeof variantString === 'string') { - streetmixData['variantString'] = variantString; - } - - // get variantString from {"O1-Name" (StreetPlan Object Name) : variantString} mapping data - const nameToVariantMap = convertRule['nameToVariantMap']; - if (nameToVariantMap && nameToVariantMap[streetplanData['O1-Name']]) { - streetmixData['variantString'] = - nameToVariantMap[streetplanData['O1-Name']]; - } - - // get variantString from {"O1-Tags" (StreetPlan Tag) : variantString} mapping data - const tagToVariantMap = convertRule['tagToVariantMap']; - if (tagToVariantMap && tagToVariantMap[streetplanData['O1-Tags']]) { - streetmixData['variantString'] = - tagToVariantMap[streetplanData['O1-Tags']]; - } - - // generate a streetMix like variantString from the listed parameter values - const variantStringKeys = convertRule['variantStringAdd']; - if (variantStringKeys) { - streetmixData['variantString'] = generateVariantString( - variantStringKeys, - streetmixData - ); - } - } - - return streetmixData; -} - -// convert streetPlan segment data to Streetmix segment data -function convertSegment(data) { - let streetmixData = {}; - const streetplanType = data['Type']; - const streetplanSubtype = data['Subtype']; - // mapping rule for current Streetplan subtypes - const subtypeMap = mapping[streetplanType]; - - // convert elevation value to Streetmix format: 0, 1, 2 - streetmixData['elevation'] = data['MaterialH'] / 0.5; - streetmixData['width'] = data['width']; - streetmixData['direction'] = directionMap[data['Direction']]; - if (data['side']) { - streetmixData['side'] = data['side']; - } - if (data['Material']) { - streetmixData['material'] = materialMap[data['Material']]; - } - - if (subtypeMap) { - const convertRule = subtypeMap[streetplanSubtype]; - if (convertRule) { - streetmixData = getDataFromSubtypeMap(convertRule, streetmixData, data); - } else { - streetmixData['type'] = streetplanType; - // STREET.notify.warningMessage(`The '${streetplanSubtype}' subtype of StreetPlan segment '${segmentType}' is not yet supported in 3DStreet`); - console.log( - `The '${streetplanSubtype}' subtype of StreetPlan segment '${streetplanType}' is not yet supported in 3DStreet` - ); - } - } else { - streetmixData['type'] = streetplanType; - // STREET.notify.warningMessage(`The '${streetplanType}' StreetPlan segment type is not yet supported in 3DStreet`); - console.log( - `The '${streetplanType}' StreetPlan segment type is not yet supported in 3DStreet` - ); - } - return streetmixData; -} - -module.exports.convertSegment = convertSegment; diff --git a/src/streetplan/streetplan-utils.js b/src/streetplan/streetplan-utils.js deleted file mode 100644 index 5d91f46a..00000000 --- a/src/streetplan/streetplan-utils.js +++ /dev/null @@ -1,87 +0,0 @@ -// utils for StreetPlan parsing -const mappingUtils = require('./conversion-map.js'); - -/** - * Convert width from feet to meters - * @param {Object} streetData - Street data containing segments - */ -function convertStreetValues(streetData) { - streetData.segments.forEach((segmentData) => { - segmentData.width *= 0.3048; - }); -} - -/** - * Convert street structure to match Streetmix JSON Schema - * @param {Object} projectData - Full project data from StreetPlan - * @returns {Object} Converted street structure - */ -function convertStreetStruct(projectData) { - // Validate input - if (!projectData || !projectData.project) { - throw new Error('Invalid project data structure'); - } - - const newStruct = { - projectName: projectData.project.ProjectName || 'Unnamed Project', - units: projectData.project.DistanceUnits || 'Feet' - }; - - // Find the first street in the project (excluding metadata keys) - const streets = Object.keys(projectData.project).filter( - (key) => key !== 'ProjectName' && key !== 'DistanceUnits' - ); - - if (streets.length === 0) { - throw new Error('No streets found in project'); - } - - const streetName = streets[0]; - newStruct.name = streetName; - - // Get the street variations (e.g. "Boulevard Alt 1", "Existing Conditions") - const variations = Object.keys(projectData.project[streetName]).filter( - (key) => key !== 'LengthMiles' - ); - - // Use the first variation by default - const selectedVariation = variations[0]; - newStruct.altName = selectedVariation; - newStruct.lengthMiles = projectData.project[streetName].LengthMiles; - - // Get segments from the selected variation - const streetData = projectData.project[streetName][selectedVariation]; - - // Remove segment indexes and convert to array - newStruct.segments = Object.values(streetData.segments); - - // Convert measurements if needed - convertStreetValues(newStruct); - - // Remove buildings and setback segments, convert remaining data - newStruct.segments = convertSegmentData(newStruct.segments).filter( - (segmentData) => { - return !['Buildings', 'setback'].includes(segmentData['type']); - } - ); - - // Add new metadata fields if present - newStruct.segments = newStruct.segments.map((segment) => { - if (segment.Group1) segment.group1 = segment.Group1; - if (segment.Group2) segment.group2 = segment.Group2; - if (segment.Cost) segment.cost = segment.Cost; - return segment; - }); - - return newStruct; -} - -function convertSegmentData(segments) { - return segments.map(mappingUtils.convertSegment); -} - -module.exports = { - convertStreetStruct, - convertSegmentData, - convertStreetValues -};