From 5e433b0ad3bdb592220fcef0c6f2261508b17848 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 11 Jul 2024 14:24:14 -0700 Subject: [PATCH 001/118] normalize markings mixins remove `markings` mixin merge its same properties to all of the striping mixins it is usually paired with also remove anisotropy --- src/assets.js | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/src/assets.js b/src/assets.js index ffdc50c84..34563d24b 100644 --- a/src/assets.js +++ b/src/assets.js @@ -134,7 +134,7 @@ function buildAssetHTML(assetUrl, categories) { - + @@ -148,19 +148,18 @@ function buildAssetHTML(assetUrl, categories) { `, 'lane-separator': ` - - - - - - - - + + + + + + + `, stencils: ` - + @@ -189,8 +188,8 @@ function buildAssetHTML(assetUrl, categories) { `, 'vehicles-transit': ` - - + + `, dividers: ` @@ -228,7 +227,7 @@ function buildAssetHTML(assetUrl, categories) { - + @@ -357,7 +356,7 @@ Unused assets kept commented here for future reference - + From c7908db5eb0a8d424fc97f311c37b68163df1996 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 11 Jul 2024 14:31:33 -0700 Subject: [PATCH 002/118] typo --- src/assets.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/assets.js b/src/assets.js index 34563d24b..2a6a82d7f 100644 --- a/src/assets.js +++ b/src/assets.js @@ -151,7 +151,7 @@ function buildAssetHTML(assetUrl, categories) { - + From e999cccea68576f4cf4c7de3cbf2e98918a5f988 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 11 Jul 2024 14:31:45 -0700 Subject: [PATCH 003/118] add striping to panel --- .../components/components/AddLayerPanel/LayersOptions.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/editor/components/components/AddLayerPanel/LayersOptions.js b/src/editor/components/components/AddLayerPanel/LayersOptions.js index ef849c39a..c6c0387b8 100644 --- a/src/editor/components/components/AddLayerPanel/LayersOptions.js +++ b/src/editor/components/components/AddLayerPanel/LayersOptions.js @@ -40,6 +40,12 @@ const LayersOptions = [ mixinGroups: ['dividers'], onClick: () => console.log('Models: dividers') }, + { + value: 'Markings: Striping', + label: 'Markings: Striping', + mixinGroups: ['lane-separator'], + onClick: () => console.log('Models: dividers') + }, { value: 'Models: Buildings', label: 'Models: Buildings', From 025d95fd92d2b59bce6bd5b2bfa376f048cc813c Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 11 Jul 2024 19:17:55 -0700 Subject: [PATCH 004/118] normalize stencils --- src/assets.js | 63 +++++++++---------- .../components/AddLayerPanel/LayersOptions.js | 8 +-- 2 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/assets.js b/src/assets.js index 2a6a82d7f..9ccff5adb 100644 --- a/src/assets.js +++ b/src/assets.js @@ -149,42 +149,41 @@ function buildAssetHTML(assetUrl, categories) { 'lane-separator': ` - - - - - - + + + + + + `, stencils: ` - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + `, 'vehicles-transit': ` diff --git a/src/editor/components/components/AddLayerPanel/LayersOptions.js b/src/editor/components/components/AddLayerPanel/LayersOptions.js index c6c0387b8..5309d172e 100644 --- a/src/editor/components/components/AddLayerPanel/LayersOptions.js +++ b/src/editor/components/components/AddLayerPanel/LayersOptions.js @@ -41,10 +41,10 @@ const LayersOptions = [ onClick: () => console.log('Models: dividers') }, { - value: 'Markings: Striping', - label: 'Markings: Striping', - mixinGroups: ['lane-separator'], - onClick: () => console.log('Models: dividers') + value: 'Markings: Stencils & Striping', + label: 'Markings: Stencils & Striping', + mixinGroups: ['lane-separator', 'stencils'], + onClick: () => console.log('Models: striping & stencils') }, { value: 'Models: Buildings', From 64965834df393a98a2daafd17ca9d16ee21acbf8 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 11 Jul 2024 21:08:46 -0700 Subject: [PATCH 005/118] add yellow versions --- src/assets.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/assets.js b/src/assets.js index 9ccff5adb..e8e61cb8f 100644 --- a/src/assets.js +++ b/src/assets.js @@ -141,7 +141,6 @@ function buildAssetHTML(assetUrl, categories) { `, 'segment-colors': ` - @@ -149,18 +148,22 @@ function buildAssetHTML(assetUrl, categories) { 'lane-separator': ` + + + `, stencils: ` + - + From c158f7e7def9a4802a2cd9b599d1b51086794e59 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 11 Jul 2024 21:09:16 -0700 Subject: [PATCH 006/118] remove reference to markings mixin --- src/aframe-streetmix-parsers.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 553d52ea8..81ba6ea9f 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -1548,25 +1548,25 @@ function processSegments( segments[i].type === 'separator' && variantList[0] === 'dashed' ) { - groundMixinId = 'markings dashed-stripe'; + groundMixinId = 'dashed-stripe'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt // for all markings material property repeat = "1 25". So every 150/25=6 meters put a dash repeatCount[0] = 1; repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'separator' && variantList[0] === 'solid') { - groundMixinId = 'markings solid-stripe'; + groundMixinId = 'solid-stripe'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'doubleyellow' ) { - groundMixinId = 'markings solid-doubleyellow'; + groundMixinId = 'solid-doubleyellow'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'shortdashedyellow' ) { - groundMixinId = 'markings yellow short-dashed-stripe'; + groundMixinId = 'short-dashed-stripe-yellow'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt // for short-dashed-stripe every 3 meters put a dash repeatCount[0] = 1; @@ -1575,13 +1575,13 @@ function processSegments( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellow' ) { - groundMixinId = 'markings yellow solid-dashed'; + groundMixinId = 'solid-dashed-yellow'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellowinverted' ) { - groundMixinId = 'markings yellow solid-dashed'; + groundMixinId = 'solid-dashed-yellow'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt rotationY = '180'; repeatCount[0] = 1; @@ -1620,7 +1620,7 @@ function processSegments( carStep = 3; markingLength = segmentWidthInMeters; markingPosX = 0; - parkingMixin = 'markings solid-stripe'; + parkingMixin = 'solid-stripe'; } const markingPosXY = markingPosX + ' 0'; const clonedStencilRadius = length / 2 - carStep; From 98e85a514f1cd493536681ee08291c2f13b58f0c Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Sep 2024 14:43:41 -0700 Subject: [PATCH 007/118] add skipcache: true --- src/assets.js | 66 +++++++++++++++++++++++++-------------------------- 1 file changed, 33 insertions(+), 33 deletions(-) diff --git a/src/assets.js b/src/assets.js index cb866b297..44aaf68d5 100644 --- a/src/assets.js +++ b/src/assets.js @@ -160,44 +160,44 @@ function buildAssetHTML(assetUrl, categories) { - - - - - - - - + + + + + + + + `, stencils: ` - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + `, 'vehicles-transit': ` From 76c1df97404b2b70a23cb1ed2bfcc7ed4502e958 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 4 Nov 2024 13:00:18 -0800 Subject: [PATCH 008/118] psuedo code street-segment --- src/components/street-segment.js | 55 ++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 src/components/street-segment.js diff --git a/src/components/street-segment.js b/src/components/street-segment.js new file mode 100644 index 000000000..c18477d89 --- /dev/null +++ b/src/components/street-segment.js @@ -0,0 +1,55 @@ +/* global AFRAME */ +AFRAME.registerComponent('street-segment', { + schema: { + type: { + type: 'string', + default: 'drive-lane', + oneOf: ['drive-lane', 'bus-lane', 'mobility-lane', 'footpath'] + }, + width: { + type: 'number' + }, + length: { + type: 'number' + }, + direction: { + type: 'string', + default: 'outbound', + oneOf: ['inbound', 'outbound'] + }, + surface: { + type: 'string', + default: 'asphalt', + oneOf: ['asphalt', 'concrete', 'grass', 'dirt', 'gravel', 'sand'] + } + }, + init: function () { + this.depth = 0.1; + this.elevation = 0; + // + }, + update: function (oldData) { + const data = this.data; + // if oldDate is not the same as data, then update the entity + if (oldData.type !== data.type) { + // TODO: this needs to use deep equal, will not work like this + } + this.clearGeometry(); + this.generateGeometry(data); + }, + generateGeometry(data) { + // create box geometry and apply to this entity + const geometry = new THREE.BoxGeometry(data.width, data.length, this.depth); + const material = new THREE.MeshBasicMaterial({ + color: 0x00ff00, + transparent: true, + opacity: 0.5 + }); + const mesh = new THREE.Mesh(geometry, material); + this.el.setObject3D('mesh', mesh); + }, + clearGeometry() { + // remove the geometry from the entity + this.el.removeObject3D('mesh'); + } +}); From 5b398c3b6d790f99aaa70575194953b3ae515c85 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 4 Nov 2024 13:43:17 -0800 Subject: [PATCH 009/118] adding some more properties --- src/components/street-segment.js | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index c18477d89..9acbf0b81 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -1,7 +1,18 @@ /* global AFRAME */ + +/* +Some next steps: +- convert streetmix parser to use street-segment instead of ground mixins defined in asset.js + + + + + + */ + AFRAME.registerComponent('street-segment', { schema: { - type: { + preset: { type: 'string', default: 'drive-lane', oneOf: ['drive-lane', 'bus-lane', 'mobility-lane', 'footpath'] @@ -21,6 +32,18 @@ AFRAME.registerComponent('street-segment', { type: 'string', default: 'asphalt', oneOf: ['asphalt', 'concrete', 'grass', 'dirt', 'gravel', 'sand'] + }, + color: { + type: 'color', + default: '#00ff00' + }, + spawn: { + // objects to spawn, model clone + type: 'array', + default: ['transit', 'cars', 'trucks'] + }, + spawnDensity: { + type: 'number' // x objects per segment } }, init: function () { From 550fc158ea3d3f2628a2f860e7fb590188c85e16 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 4 Nov 2024 15:46:56 -0800 Subject: [PATCH 010/118] barely working wip --- src/aframe-streetmix-parsers.js | 30 ++--------- src/components/street-segment.js | 88 ++++++++++++++++++++++++++------ src/index.js | 1 + 3 files changed, 77 insertions(+), 42 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 28160122a..8f560ce31 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -898,6 +898,7 @@ function createCenteredStreetElement(segments) { return streetEl; } +// instead, create entity with street-segment component function createSegmentElement( segmentWidthInMeters, positionY, @@ -907,32 +908,11 @@ function createSegmentElement( elevation = 0 ) { var segmentEl = document.createElement('a-entity'); - const heightLevels = [0.2, 0.4, 0.6]; - const height = heightLevels[elevation]; - if (elevation === 0) { - positionY = -0.1; - } else if (elevation === 2) { - positionY = 0.1; - } - - segmentEl.setAttribute( - 'geometry', - `primitive: box; - height: ${height}; - depth: ${length}; - width: ${segmentWidthInMeters};` - ); - + segmentEl.setAttribute('street-segment', 'preset', mixinId); + segmentEl.setAttribute('street-segment', 'width', segmentWidthInMeters); + segmentEl.setAttribute('street-segment', 'length', length); + segmentEl.setAttribute('street-segment', 'elevation', elevation); segmentEl.setAttribute('position', { y: positionY }); - segmentEl.setAttribute('mixin', mixinId); - - if (repeatCount.length !== 0) { - segmentEl.setAttribute( - 'material', - `repeat: ${repeatCount[0]} ${repeatCount[1]}` - ); - } - return segmentEl; } diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 9acbf0b81..a04729443 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -15,7 +15,7 @@ AFRAME.registerComponent('street-segment', { preset: { type: 'string', default: 'drive-lane', - oneOf: ['drive-lane', 'bus-lane', 'mobility-lane', 'footpath'] + oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk'] }, width: { type: 'number' @@ -23,6 +23,10 @@ AFRAME.registerComponent('street-segment', { length: { type: 'number' }, + elevation: { + type: 'number', + default: 0 + }, direction: { type: 'string', default: 'outbound', @@ -47,32 +51,82 @@ AFRAME.registerComponent('street-segment', { } }, init: function () { - this.depth = 0.1; - this.elevation = 0; - // + this.height = 0.2; // default height of segment surface box }, update: function (oldData) { const data = this.data; - // if oldDate is not the same as data, then update the entity - if (oldData.type !== data.type) { + // if oldDate is same as current data, then don't update + if (AFRAME.utils.deepEqual(oldData, data)) { + return; // TODO: this needs to use deep equal, will not work like this } this.clearGeometry(); + this.calculateHeight(data.elevation); + this.el.setAttribute( + 'position', + 'y', + this.calculateYPosition(data.elevation) + ); this.generateGeometry(data); }, - generateGeometry(data) { - // create box geometry and apply to this entity - const geometry = new THREE.BoxGeometry(data.width, data.length, this.depth); - const material = new THREE.MeshBasicMaterial({ - color: 0x00ff00, - transparent: true, - opacity: 0.5 - }); - const mesh = new THREE.Mesh(geometry, material); - this.el.setObject3D('mesh', mesh); + // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units + calculateHeight: function (elevation) { + const heightLevels = [0.2, 0.4, 0.6]; + if (elevation === -1) { + this.height = 0; + return; + } + this.height = heightLevels[elevation]; + return; + }, + calculateYPosition: function (elevation) { + let positionY; + if (this.data.elevation === 0) { + positionY = -0.1; + } else if (elevation === 2) { + positionY = 0.1; + } else { + positionY = 0; + } + return positionY; }, - clearGeometry() { + clearGeometry: function () { // remove the geometry from the entity + this.el.setAttribute('geometry', ''); + this.el.setAttribute('material', ''); this.el.removeObject3D('mesh'); + }, + remove: function () { + this.clearGeometry(); + }, + generateGeometry: function (data) { + this.el.setAttribute( + 'geometry', + `primitive: box; + height: ${this.height}; + depth: ${this.data.length}; + width: ${this.data.width};` + ); + + this.el.setAttribute('mixin', this.data.preset); + + // if (repeatCount.length !== 0) { + // segmentEl.setAttribute( + // 'material', + // `repeat: ${repeatCount[0]} ${repeatCount[1]}` + // ); + // } + + return; + + // // create box geometry and apply to this entity + // const geometry = new THREE.BoxGeometry(data.width, data.length, this.depth); + // const material = new THREE.MeshBasicMaterial({ + // color: 0x00ff00, + // transparent: true, + // opacity: 0.5 + // }); + // const mesh = new THREE.Mesh(geometry, material); + // this.el.setObject3D('mesh', mesh); } }); diff --git a/src/index.js b/src/index.js index de43a4a9f..166f391a1 100644 --- a/src/index.js +++ b/src/index.js @@ -20,6 +20,7 @@ require('./components/street-geo.js'); require('./components/street-environment.js'); require('./components/intersection.js'); require('./components/obb-clipping.js'); +require('./components/street-segment.js'); if (typeof VERSION !== 'undefined') { console.log(`3DStreet Version: ${VERSION}`); From b5bd40a0f3467d08f1ec4ee4e9163489ad66fa32 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 4 Nov 2024 16:28:43 -0800 Subject: [PATCH 011/118] mvp allow changing surface --- src/components/street-segment.js | 38 +++++++++++++++++++++++--------- 1 file changed, 28 insertions(+), 10 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index a04729443..46666ef6c 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -15,7 +15,7 @@ AFRAME.registerComponent('street-segment', { preset: { type: 'string', default: 'drive-lane', - oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk'] + oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk', 'parking'] }, width: { type: 'number' @@ -35,7 +35,7 @@ AFRAME.registerComponent('street-segment', { surface: { type: 'string', default: 'asphalt', - oneOf: ['asphalt', 'concrete', 'grass', 'dirt', 'gravel', 'sand'] + oneOf: ['asphalt', 'concrete', 'grass', 'sidewalk', 'gravel', 'sand'] }, color: { type: 'color', @@ -60,7 +60,7 @@ AFRAME.registerComponent('street-segment', { return; // TODO: this needs to use deep equal, will not work like this } - this.clearGeometry(); + // this.clearGeometry(); this.calculateHeight(data.elevation); this.el.setAttribute( 'position', @@ -92,23 +92,41 @@ AFRAME.registerComponent('street-segment', { }, clearGeometry: function () { // remove the geometry from the entity - this.el.setAttribute('geometry', ''); - this.el.setAttribute('material', ''); - this.el.removeObject3D('mesh'); + this.el.removeAttribute('geometry'); + this.el.removeAttribute('material'); + // this.el.removeObject3D('mesh'); }, remove: function () { this.clearGeometry(); }, generateGeometry: function (data) { + // create a lookup table for the material presets + const textureMaps = { + asphalt: 'seamless-road', + concrete: 'seamless-bright-road', + grass: 'grass-texture', + sidewalk: 'seamless-sidewalk', + gravel: 'compacted-gravel-texture', + sand: 'sandy-asphalt-texture' + }; + // this.el.setAttribute('mixin', this.data.preset); + + // set the material based on the textureMap + let textureSourceId = textureMaps[this.data.surface]; + console.log('textureSourceId', textureSourceId); + this.el.setAttribute( 'geometry', `primitive: box; - height: ${this.height}; - depth: ${this.data.length}; - width: ${this.data.width};` + height: ${this.height}; + depth: ${this.data.length}; + width: ${this.data.width};` ); - this.el.setAttribute('mixin', this.data.preset); + this.el.setAttribute( + 'material', + `src: #${textureMaps[this.data.surface]}; roughness: 0.8; repeat: 0.3 25; offset: 0.55 0` + ); // if (repeatCount.length !== 0) { // segmentEl.setAttribute( From 415cdb9a5b04fd114248f23b1d2ae40fc7f235f4 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 4 Nov 2024 21:35:32 -0800 Subject: [PATCH 012/118] next wip cut --- src/components/street-segment.js | 90 ++++++++++++++++++++++++-------- 1 file changed, 68 insertions(+), 22 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 46666ef6c..08979107b 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -15,7 +15,7 @@ AFRAME.registerComponent('street-segment', { preset: { type: 'string', default: 'drive-lane', - oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk', 'parking'] + oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk', 'parking-lane'] }, width: { type: 'number' @@ -38,8 +38,7 @@ AFRAME.registerComponent('street-segment', { oneOf: ['asphalt', 'concrete', 'grass', 'sidewalk', 'gravel', 'sand'] }, color: { - type: 'color', - default: '#00ff00' + type: 'color' }, spawn: { // objects to spawn, model clone @@ -52,22 +51,75 @@ AFRAME.registerComponent('street-segment', { }, init: function () { this.height = 0.2; // default height of segment surface box + // parse preset into default surface, color + this.applyPreset(this.data.preset); + }, + applyPreset: function (preset) { + // parse preset into + // default surface, color + const presets = { + 'drive-lane': { + surface: 'asphalt' + }, + 'bus-lane': { + surface: 'asphalt', + color: '#ff9393' + }, + 'surface-red bus-lane': { + // legacy output from processSegments + surface: 'asphalt', + color: '#ff9393' + }, + 'bike-lane': { + surface: 'asphalt', + color: '#adff83' + }, + 'surface-green bike-lane': { + // legacy output from processSegments + surface: 'asphalt', + color: '#adff83' + }, + sidewalk: { + surface: 'sidewalk' + }, + 'parking-lane': { + surface: 'concrete' + }, + 'bright-lane': { + // legacy output for 'parking-lane' from processSegments + surface: 'concrete' + } + }; + // if preset is not found, then use default preset + if (!presets[preset]) { + preset = 'drive-lane'; + } + this.el.setAttribute('street-segment', 'surface', presets[preset].surface); + if (presets[preset].color) { + this.el.setAttribute('street-segment', 'color', presets[preset].color); + } else { + this.el.setAttribute('street-segment', 'color', '#ffffff'); + } }, update: function (oldData) { const data = this.data; - // if oldDate is same as current data, then don't update + // if oldData is same as current data, then don't update if (AFRAME.utils.deepEqual(oldData, data)) { return; - // TODO: this needs to use deep equal, will not work like this } - // this.clearGeometry(); + // if oldData is defined AND the "preset" property has changed, then update + if (oldData.preset !== undefined && oldData.preset !== data.preset) { + this.applyPreset(data.preset); + return; + } + this.clearMesh(); this.calculateHeight(data.elevation); this.el.setAttribute( 'position', 'y', this.calculateYPosition(data.elevation) ); - this.generateGeometry(data); + this.generateMesh(data); }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units calculateHeight: function (elevation) { @@ -90,16 +142,16 @@ AFRAME.registerComponent('street-segment', { } return positionY; }, - clearGeometry: function () { + clearMesh: function () { // remove the geometry from the entity this.el.removeAttribute('geometry'); this.el.removeAttribute('material'); // this.el.removeObject3D('mesh'); }, remove: function () { - this.clearGeometry(); + this.clearMesh(); }, - generateGeometry: function (data) { + generateMesh: function (data) { // create a lookup table for the material presets const textureMaps = { asphalt: 'seamless-road', @@ -125,26 +177,20 @@ AFRAME.registerComponent('street-segment', { this.el.setAttribute( 'material', - `src: #${textureMaps[this.data.surface]}; roughness: 0.8; repeat: 0.3 25; offset: 0.55 0` + `src: #${textureMaps[this.data.surface]}; + roughness: 0.8; + repeat: 0.3 25; + offset: 0.55 0; + color: ${this.data.color}` ); + // TODO: fix repeating values (depends on surface value chosen) // if (repeatCount.length !== 0) { // segmentEl.setAttribute( // 'material', // `repeat: ${repeatCount[0]} ${repeatCount[1]}` // ); // } - return; - - // // create box geometry and apply to this entity - // const geometry = new THREE.BoxGeometry(data.width, data.length, this.depth); - // const material = new THREE.MeshBasicMaterial({ - // color: 0x00ff00, - // transparent: true, - // opacity: 0.5 - // }); - // const mesh = new THREE.Mesh(geometry, material); - // this.el.setObject3D('mesh', mesh); } }); From 00e12a8ef80b5d9a038d248cc07b7241be8be8ca Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 5 Nov 2024 13:27:56 -0800 Subject: [PATCH 013/118] next cut of working street-segment component --- src/components/street-segment.js | 59 +++++++++++++++++++++++--------- 1 file changed, 43 insertions(+), 16 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 08979107b..15944e063 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -133,7 +133,7 @@ AFRAME.registerComponent('street-segment', { }, calculateYPosition: function (elevation) { let positionY; - if (this.data.elevation === 0) { + if (elevation === 0) { positionY = -0.1; } else if (elevation === 2) { positionY = 0.1; @@ -161,36 +161,63 @@ AFRAME.registerComponent('street-segment', { gravel: 'compacted-gravel-texture', sand: 'sandy-asphalt-texture' }; - // this.el.setAttribute('mixin', this.data.preset); // set the material based on the textureMap - let textureSourceId = textureMaps[this.data.surface]; + let textureSourceId = textureMaps[data.surface]; console.log('textureSourceId', textureSourceId); this.el.setAttribute( 'geometry', `primitive: box; height: ${this.height}; - depth: ${this.data.length}; - width: ${this.data.width};` + depth: ${data.length}; + width: ${data.width};` + ); + + // calculate the repeatCount for the material + let repeatX, repeatY, offsetX; + [repeatX, repeatY, offsetX] = this.calculateTextureRepeat( + data.length, + data.width, + textureSourceId ); this.el.setAttribute( 'material', - `src: #${textureMaps[this.data.surface]}; + `src: #${textureMaps[data.surface]}; roughness: 0.8; - repeat: 0.3 25; - offset: 0.55 0; - color: ${this.data.color}` + repeat: ${repeatX} ${repeatY}; + offset: ${offsetX} 0; + color: ${data.color}` ); - // TODO: fix repeating values (depends on surface value chosen) - // if (repeatCount.length !== 0) { - // segmentEl.setAttribute( - // 'material', - // `repeat: ${repeatCount[0]} ${repeatCount[1]}` - // ); - // } + this.el.setAttribute('shadow', 'cast: false'); + return; + }, + calculateTextureRepeat: function (length, width, textureSourceId) { + // calculate the repeatCount for the material + let repeatX = 0.3; // drive-lane, bus-lane, bike-lane + let repeatY = length / 6; + let offsetX = 0.55; + if (textureSourceId === 'seamless-bright-road') { + repeatX = 0.6; + repeatY = 15; + } else if (textureSourceId === 'seamless-sandy-road') { + // desired outcome for 60m length is = repeatx 0.15, repeaty 2.5 + // 0.15 = 3 / x => + repeatX = width / 30; + repeatY = length / 30; + offsetX = 0; + } else if (textureSourceId === 'seamless-sidewalk') { + repeatX = width / 2; + repeatY = length / 2; + offsetX = 0; + } else if (textureSourceId === 'grass-texture') { + repeatX = width / 4; + repeatY = length / 6; + offsetX = 0; + } + return [repeatX, repeatY, offsetX]; } }); From 8349516013ef30947a1409aea55bc65da80a474a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 5 Nov 2024 20:19:24 -0800 Subject: [PATCH 014/118] specify color from streetmix-parser --- src/aframe-streetmix-parsers.js | 55 ++++++++++++++------------------ src/components/street-segment.js | 41 ++++++++---------------- 2 files changed, 38 insertions(+), 58 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 8f560ce31..d2de7b2b1 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -4,6 +4,15 @@ var streetmixParsersTested = require('./tested/aframe-streetmix-parsers-tested'); var { segmentVariants } = require('./segments-variants.js'); +var COLORS = { + red: '#ff9393', + blue: '#00b6b6', + green: '#adff83', + yellow: '#f7d117', + white: '#ffffff', + brown: '#664B00' +}; + function cloneMixinAsChildren({ objectMixinId = '', parentEl = null, @@ -338,30 +347,17 @@ function createSidewalkClonedVariants( return dividerParentEl; } -function getBikeLaneMixin(variant) { - if (variant === 'red') { - return 'surface-red bike-lane'; - } - if (variant === 'blue') { - return 'surface-blue bike-lane'; - } - if (variant === 'green') { - return 'surface-green bike-lane'; - } - return 'bike-lane'; -} - -function getBusLaneMixin(variant) { +function getSegmentColor(variant) { if ((variant === 'colored') | (variant === 'red')) { - return 'surface-red bus-lane'; + return COLORS.red; } if (variant === 'blue') { - return 'surface-blue bus-lane'; + return COLORS.blue; } if (variant === 'grass') { - return 'surface-green bus-lane'; + return COLORS.green; } - return 'bus-lane'; + return COLORS.white; } function getDimensions(object3d) { @@ -905,13 +901,15 @@ function createSegmentElement( mixinId, length, repeatCount, - elevation = 0 + elevation = 0, + color ) { var segmentEl = document.createElement('a-entity'); segmentEl.setAttribute('street-segment', 'preset', mixinId); segmentEl.setAttribute('street-segment', 'width', segmentWidthInMeters); segmentEl.setAttribute('street-segment', 'length', length); segmentEl.setAttribute('street-segment', 'elevation', elevation); + segmentEl.setAttribute('street-segment', 'color', color); segmentEl.setAttribute('position', { y: positionY }); return segmentEl; } @@ -992,6 +990,7 @@ function processSegments( var cumulativeWidthInMeters = 0; for (var i = 0; i < segments.length; i++) { + var segmentColor = COLORS.white; var segmentParentEl = document.createElement('a-entity'); segmentParentEl.classList.add('segment-parent-' + i); @@ -1057,7 +1056,7 @@ function processSegments( y: elevationPosY + 0.015 }); // get the mixin id for a bike lane - groundMixinId = getBikeLaneMixin(variantList[1]); + segmentColor = getSegmentColor(variantList[1]); // clone a bunch of stencil entities (note: this is not draw call efficient) cloneMixinAsChildren({ objectMixinId: 'stencils bike-arrow', @@ -1083,7 +1082,7 @@ function processSegments( segments[i].type === 'streetcar' ) { // get the mixin id for a bus lane - groundMixinId = getBusLaneMixin(variantList[1]); + segmentColor = getSegmentColor(variantList[1]); // get the mixin id for the vehicle (is it a trolley or a tram?) var objectMixinId = segments[i].type === 'streetcar' ? 'trolley' : 'tram'; // create and append a train element @@ -1262,7 +1261,7 @@ function processSegments( segments[i].type === 'bus-lane' || segments[i].type === 'brt-lane' ) { - groundMixinId = getBusLaneMixin(variantList[1]); + segmentColor = getSegmentColor(variantList[1]); segmentParentEl.append( createBusElement(variantList, length, showVehicles) @@ -1647,13 +1646,6 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); } - if (streetmixParsersTested.isSidewalk(segments[i].type)) { - groundMixinId = 'sidewalk'; - repeatCount[0] = segmentWidthInMeters / 1.5; - // every 2 meters repeat sidewalk texture - repeatCount[1] = parseInt(length / 2); - } - // add new object if (segments[i].type !== 'separator') { segmentParentEl.append( @@ -1663,7 +1655,8 @@ function processSegments( groundMixinId, length, repeatCount, - elevation + elevation, + segmentColor ) ); } else { @@ -1694,7 +1687,7 @@ function processSegments( dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 dirtBox.setAttribute('width', cumulativeWidthInMeters); dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side - dirtBox.setAttribute('material', 'color: #664B00;'); + dirtBox.setAttribute('material', `color: ${COLORS.brown};`); dirtBox.setAttribute('data-layer-name', 'Underground'); streetParentEl.append(dirtBox); return streetParentEl; diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 15944e063..28435235a 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -1,14 +1,20 @@ /* global AFRAME */ /* -Some next steps: -- convert streetmix parser to use street-segment instead of ground mixins defined in asset.js - - */ +*/ + +var COLORS = { + red: '#ff9393', + blue: '#00b6b6', + green: '#adff83', + yellow: '#f7d117', + white: '#ffffff', + brown: '#664B00' +}; AFRAME.registerComponent('street-segment', { schema: { @@ -39,14 +45,6 @@ AFRAME.registerComponent('street-segment', { }, color: { type: 'color' - }, - spawn: { - // objects to spawn, model clone - type: 'array', - default: ['transit', 'cars', 'trucks'] - }, - spawnDensity: { - type: 'number' // x objects per segment } }, init: function () { @@ -63,21 +61,11 @@ AFRAME.registerComponent('street-segment', { }, 'bus-lane': { surface: 'asphalt', - color: '#ff9393' - }, - 'surface-red bus-lane': { - // legacy output from processSegments - surface: 'asphalt', - color: '#ff9393' + color: COLORS.red }, 'bike-lane': { surface: 'asphalt', - color: '#adff83' - }, - 'surface-green bike-lane': { - // legacy output from processSegments - surface: 'asphalt', - color: '#adff83' + color: COLORS.green }, sidewalk: { surface: 'sidewalk' @@ -204,8 +192,6 @@ AFRAME.registerComponent('street-segment', { repeatX = 0.6; repeatY = 15; } else if (textureSourceId === 'seamless-sandy-road') { - // desired outcome for 60m length is = repeatx 0.15, repeaty 2.5 - // 0.15 = 3 / x => repeatX = width / 30; repeatY = length / 30; offsetX = 0; @@ -217,7 +203,8 @@ AFRAME.registerComponent('street-segment', { repeatX = width / 4; repeatY = length / 6; offsetX = 0; - } + } // still need to support hatched-base + // how to handle different surface materials from streetmix return [repeatX, repeatY, offsetX]; } }); From efe9ed1cb77053933437b6fb645a1daf3d7159cf Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 5 Nov 2024 20:47:24 -0800 Subject: [PATCH 015/118] sidewalks working as expected --- src/aframe-streetmix-parsers.js | 62 +++++++++++++++++--------------- src/components/street-segment.js | 34 +++++++++--------- 2 files changed, 49 insertions(+), 47 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index d2de7b2b1..e34beb833 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -1026,7 +1026,7 @@ function processSegments( variantList[0] === 'outbound' || variantList[1] === 'outbound' ? 1 : -1; // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that - var groundMixinId = segments[i].type; + var segmentPreset = segments[i].type; // repeat value for material property - repeatCount[0] is x texture repeat and repeatCount[1] is y texture repeat const repeatCount = []; @@ -1094,7 +1094,7 @@ function processSegments( // add these trains to the segment parent segmentParentEl.append(tracksParentEl); } else if (segments[i].type === 'turn-lane') { - groundMixinId = 'drive-lane'; // use normal drive lane road material + segmentPreset = 'drive-lane'; // use normal drive lane road material var markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name // Fix streetmix inbound turn lane orientation (change left to right) per: https://github.com/streetmix/streetmix/issues/683 @@ -1141,7 +1141,7 @@ function processSegments( segmentParentEl.append(stencilsParentEl); } } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { - groundMixinId = 'divider'; + segmentPreset = 'divider'; // make some bollards const bollardsParentEl = createBollardsParentElement(); cloneMixinAsChildren({ @@ -1155,7 +1155,7 @@ function processSegments( repeatCount[0] = 1; repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { - groundMixinId = 'grass'; + segmentPreset = 'grass'; segmentParentEl.append( createDividerVariant('flowers', clonedObjectRadius, 2.25) ); @@ -1163,7 +1163,7 @@ function processSegments( segments[i].type === 'divider' && variantList[0] === 'planting-strip' ) { - groundMixinId = 'grass'; + segmentPreset = 'grass'; segmentParentEl.append( createDividerVariant('planting-strip', clonedObjectRadius, 2.25) ); @@ -1171,7 +1171,7 @@ function processSegments( segments[i].type === 'divider' && variantList[0] === 'planter-box' ) { - groundMixinId = 'grass'; + segmentPreset = 'grass'; segmentParentEl.append( createDividerVariant('planter-box', clonedObjectRadius, 2.45) ); @@ -1179,7 +1179,7 @@ function processSegments( segments[i].type === 'divider' && variantList[0] === 'palm-tree' ) { - groundMixinId = 'grass'; + segmentPreset = 'grass'; const treesParentEl = createTreesParentElement(); cloneMixinAsChildren({ objectMixinId: 'palm-tree', @@ -1192,7 +1192,7 @@ function processSegments( segments[i].type === 'divider' && variantList[0] === 'big-tree' ) { - groundMixinId = 'grass'; + segmentPreset = 'grass'; const treesParentEl = createTreesParentElement(); cloneMixinAsChildren({ objectMixinId: 'tree3', @@ -1202,26 +1202,26 @@ function processSegments( }); segmentParentEl.append(treesParentEl); } else if (segments[i].type === 'divider' && variantList[0] === 'bush') { - groundMixinId = 'grass'; + segmentPreset = 'grass'; segmentParentEl.append( createDividerVariant('bush', clonedObjectRadius, 2.25) ); } else if (segments[i].type === 'divider' && variantList[0] === 'dome') { - groundMixinId = 'divider'; + segmentPreset = 'divider'; segmentParentEl.append( createDividerVariant('dome', clonedObjectRadius, 2.25) ); repeatCount[0] = 1; repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider') { - groundMixinId = 'divider'; + segmentPreset = 'divider'; repeatCount[0] = 1; repeatCount[1] = parseInt(length) / 4; } else if ( segments[i].type === 'temporary' && variantList[0] === 'barricade' ) { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append( createClonedVariants('temporary-barricade', clonedObjectRadius, 2.25) ); @@ -1229,7 +1229,7 @@ function processSegments( segments[i].type === 'temporary' && variantList[0] === 'traffic-cone' ) { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append( createClonedVariants('temporary-traffic-cone', clonedObjectRadius, 2.25) ); @@ -1237,7 +1237,7 @@ function processSegments( segments[i].type === 'temporary' && variantList[0] === 'jersey-barrier-plastic' ) { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append( createClonedVariants( 'temporary-jersey-barrier-plastic', @@ -1249,7 +1249,7 @@ function processSegments( segments[i].type === 'temporary' && variantList[0] === 'jersey-barrier-concrete' ) { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append( createClonedVariants( 'temporary-jersey-barrier-concrete', @@ -1326,10 +1326,10 @@ function processSegments( ) ); } else if (segments[i].type === 'food-truck') { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append(createFoodTruckElement(variantList, length)); } else if (segments[i].type === 'flex-zone') { - groundMixinId = 'bright-lane'; + segmentPreset = 'parking-lane'; segmentParentEl.append( createFlexZoneElement(variantList, length, showVehicles) ); @@ -1417,13 +1417,13 @@ function processSegments( // add bike racks to the segment parent segmentParentEl.append(bikeRacksParentEl); } else if (segments[i].type === 'magic-carpet') { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append(createMagicCarpetElement(showVehicles)); } else if (segments[i].type === 'outdoor-dining') { - groundMixinId = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; + segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; segmentParentEl.append(createOutdoorDining(length, elevationPosY)); } else if (segments[i].type === 'parklet') { - groundMixinId = 'drive-lane'; + segmentPreset = 'drive-lane'; segmentParentEl.append(createParkletElement(length, variantList)); } else if (segments[i].type === 'bikeshare') { // make the parent for all the stations @@ -1527,25 +1527,25 @@ function processSegments( segments[i].type === 'separator' && variantList[0] === 'dashed' ) { - groundMixinId = 'markings dashed-stripe'; + segmentPreset = 'markings dashed-stripe'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt // for all markings material property repeat = "1 25". So every 150/25=6 meters put a dash repeatCount[0] = 1; repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'separator' && variantList[0] === 'solid') { - groundMixinId = 'markings solid-stripe'; + segmentPreset = 'markings solid-stripe'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'doubleyellow' ) { - groundMixinId = 'markings solid-doubleyellow'; + segmentPreset = 'markings solid-doubleyellow'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'shortdashedyellow' ) { - groundMixinId = 'markings yellow short-dashed-stripe'; + segmentPreset = 'markings yellow short-dashed-stripe'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt // for short-dashed-stripe every 3 meters put a dash repeatCount[0] = 1; @@ -1554,13 +1554,13 @@ function processSegments( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellow' ) { - groundMixinId = 'markings yellow solid-dashed'; + segmentPreset = 'markings yellow solid-dashed'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellowinverted' ) { - groundMixinId = 'markings yellow solid-dashed'; + segmentPreset = 'markings yellow solid-dashed'; positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt rotationY = '180'; repeatCount[0] = 1; @@ -1568,7 +1568,7 @@ function processSegments( } else if (segments[i].type === 'parking-lane') { let reusableObjectStencilsParentEl; - groundMixinId = 'bright-lane'; + segmentPreset = 'bright-lane'; let parkingMixin = 'stencils parking-t'; const carCount = 5; @@ -1646,13 +1646,17 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); } + // if this thing is a sidewalk, make segmentPreset sidewalk + if (streetmixParsersTested.isSidewalk(segments[i].type)) { + segmentPreset = 'sidewalk'; + } // add new object if (segments[i].type !== 'separator') { segmentParentEl.append( createSegmentElement( segmentWidthInMeters, positionY, - groundMixinId, + segmentPreset, length, repeatCount, elevation, @@ -1664,7 +1668,7 @@ function processSegments( createSeparatorElement( positionY, rotationY, - groundMixinId, + segmentPreset, length, repeatCount, elevation diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 28435235a..fc35f15d5 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -73,9 +73,11 @@ AFRAME.registerComponent('street-segment', { 'parking-lane': { surface: 'concrete' }, - 'bright-lane': { - // legacy output for 'parking-lane' from processSegments - surface: 'concrete' + divider: { + surface: 'hatched' + }, + grass: { + surface: 'grass' } }; // if preset is not found, then use default preset @@ -134,13 +136,21 @@ AFRAME.registerComponent('street-segment', { // remove the geometry from the entity this.el.removeAttribute('geometry'); this.el.removeAttribute('material'); - // this.el.removeObject3D('mesh'); }, remove: function () { this.clearMesh(); }, generateMesh: function (data) { - // create a lookup table for the material presets + // create geometry + this.el.setAttribute( + 'geometry', + `primitive: box; + height: ${this.height}; + depth: ${data.length}; + width: ${data.width};` + ); + + // create a lookup table to convert UI shortname into A-Frame img id's const textureMaps = { asphalt: 'seamless-road', concrete: 'seamless-bright-road', @@ -149,22 +159,10 @@ AFRAME.registerComponent('street-segment', { gravel: 'compacted-gravel-texture', sand: 'sandy-asphalt-texture' }; - - // set the material based on the textureMap let textureSourceId = textureMaps[data.surface]; - console.log('textureSourceId', textureSourceId); - - this.el.setAttribute( - 'geometry', - `primitive: box; - height: ${this.height}; - depth: ${data.length}; - width: ${data.width};` - ); // calculate the repeatCount for the material - let repeatX, repeatY, offsetX; - [repeatX, repeatY, offsetX] = this.calculateTextureRepeat( + let [repeatX, repeatY, offsetX] = this.calculateTextureRepeat( data.length, data.width, textureSourceId From 221f2835b27b4aeb0cb73895c57b0392663e12ae Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 5 Nov 2024 21:55:11 -0800 Subject: [PATCH 016/118] add hatched base, remove color from presets need to specify color in streetmix parsers --- src/components/street-segment.js | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index fc35f15d5..37167660c 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -7,15 +7,6 @@ */ -var COLORS = { - red: '#ff9393', - blue: '#00b6b6', - green: '#adff83', - yellow: '#f7d117', - white: '#ffffff', - brown: '#664B00' -}; - AFRAME.registerComponent('street-segment', { schema: { preset: { @@ -60,12 +51,10 @@ AFRAME.registerComponent('street-segment', { surface: 'asphalt' }, 'bus-lane': { - surface: 'asphalt', - color: COLORS.red + surface: 'asphalt' }, 'bike-lane': { - surface: 'asphalt', - color: COLORS.green + surface: 'asphalt' }, sidewalk: { surface: 'sidewalk' @@ -157,7 +146,8 @@ AFRAME.registerComponent('street-segment', { grass: 'grass-texture', sidewalk: 'seamless-sidewalk', gravel: 'compacted-gravel-texture', - sand: 'sandy-asphalt-texture' + sand: 'sandy-asphalt-texture', + hatched: 'hatched-base' }; let textureSourceId = textureMaps[data.surface]; @@ -201,7 +191,12 @@ AFRAME.registerComponent('street-segment', { repeatX = width / 4; repeatY = length / 6; offsetX = 0; - } // still need to support hatched-base + } else if (textureSourceId === 'hatched-base') { + repeatX = 1; + repeatY = length / 4; + offsetX = 0; + } + // still need to support hatched-base // how to handle different surface materials from streetmix return [repeatX, repeatY, offsetX]; } From f9ac436340f85bb8ff0567a11ae9c30b808a1502 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 5 Nov 2024 22:28:38 -0800 Subject: [PATCH 017/118] color sets correctly from parsers --- src/aframe-streetmix-parsers.js | 10 ++-------- src/components/street-segment.js | 5 ----- 2 files changed, 2 insertions(+), 13 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index e34beb833..c0130d7d2 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -348,13 +348,13 @@ function createSidewalkClonedVariants( } function getSegmentColor(variant) { - if ((variant === 'colored') | (variant === 'red')) { + if ((variant === 'red') | (variant === 'colored')) { return COLORS.red; } if (variant === 'blue') { return COLORS.blue; } - if (variant === 'grass') { + if ((variant === 'green') | (variant === 'grass')) { return COLORS.green; } return COLORS.white; @@ -1152,8 +1152,6 @@ function processSegments( }); // add the bollards to the segment parent segmentParentEl.append(bollardsParentEl); - repeatCount[0] = 1; - repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { segmentPreset = 'grass'; segmentParentEl.append( @@ -1211,12 +1209,8 @@ function processSegments( segmentParentEl.append( createDividerVariant('dome', clonedObjectRadius, 2.25) ); - repeatCount[0] = 1; - repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider') { segmentPreset = 'divider'; - repeatCount[0] = 1; - repeatCount[1] = parseInt(length) / 4; } else if ( segments[i].type === 'temporary' && variantList[0] === 'barricade' diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 37167660c..5dc0bea96 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -74,11 +74,6 @@ AFRAME.registerComponent('street-segment', { preset = 'drive-lane'; } this.el.setAttribute('street-segment', 'surface', presets[preset].surface); - if (presets[preset].color) { - this.el.setAttribute('street-segment', 'color', presets[preset].color); - } else { - this.el.setAttribute('street-segment', 'color', '#ffffff'); - } }, update: function (oldData) { const data = this.data; From 0248c51aa2bde5be23059c78b6176d18b1b214a8 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 6 Nov 2024 15:41:26 -0800 Subject: [PATCH 018/118] add street-segment component to segment parent, fix surface elevation --- src/aframe-streetmix-parsers.js | 39 ++++++++------------------------ src/components/street-segment.js | 16 ++++++------- 2 files changed, 16 insertions(+), 39 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index c0130d7d2..1a1af8d8d 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -894,26 +894,6 @@ function createCenteredStreetElement(segments) { return streetEl; } -// instead, create entity with street-segment component -function createSegmentElement( - segmentWidthInMeters, - positionY, - mixinId, - length, - repeatCount, - elevation = 0, - color -) { - var segmentEl = document.createElement('a-entity'); - segmentEl.setAttribute('street-segment', 'preset', mixinId); - segmentEl.setAttribute('street-segment', 'width', segmentWidthInMeters); - segmentEl.setAttribute('street-segment', 'length', length); - segmentEl.setAttribute('street-segment', 'elevation', elevation); - segmentEl.setAttribute('street-segment', 'color', color); - segmentEl.setAttribute('position', { y: positionY }); - return segmentEl; -} - function createSeparatorElement( positionY, rotationY, @@ -1646,17 +1626,16 @@ function processSegments( } // add new object if (segments[i].type !== 'separator') { - segmentParentEl.append( - createSegmentElement( - segmentWidthInMeters, - positionY, - segmentPreset, - length, - repeatCount, - elevation, - segmentColor - ) + segmentParentEl.setAttribute('street-segment', 'preset', segmentPreset); + segmentParentEl.setAttribute( + 'street-segment', + 'width', + segmentWidthInMeters ); + segmentParentEl.setAttribute('street-segment', 'length', length); + segmentParentEl.setAttribute('street-segment', 'elevation', elevation); + segmentParentEl.setAttribute('street-segment', 'color', segmentColor); + // segmentParentEl.setAttribute('position', { y: positionY }); } else { segmentParentEl.append( createSeparatorElement( diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 5dc0bea96..74d66ca36 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -21,7 +21,7 @@ AFRAME.registerComponent('street-segment', { type: 'number' }, elevation: { - type: 'number', + type: 'int', default: 0 }, direction: { @@ -83,23 +83,19 @@ AFRAME.registerComponent('street-segment', { } // if oldData is defined AND the "preset" property has changed, then update if (oldData.preset !== undefined && oldData.preset !== data.preset) { - this.applyPreset(data.preset); + this.applyPreset(data.preset, true); return; } this.clearMesh(); this.calculateHeight(data.elevation); - this.el.setAttribute( - 'position', - 'y', - this.calculateYPosition(data.elevation) - ); + this.el.object3D.position.y = this.calculateYPosition(data.elevation); // Direct Object3D manipulation this.generateMesh(data); }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units calculateHeight: function (elevation) { const heightLevels = [0.2, 0.4, 0.6]; if (elevation === -1) { - this.height = 0; + this.height = 0.2; return; } this.height = heightLevels[elevation]; @@ -111,8 +107,10 @@ AFRAME.registerComponent('street-segment', { positionY = -0.1; } else if (elevation === 2) { positionY = 0.1; - } else { + } else if (elevation === 1) { positionY = 0; + } else if (elevation === -1) { + positionY = -0.2; } return positionY; }, From 47c1ddbf5e39036bffbbb02fe126a1bf821f8702 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 6 Nov 2024 16:17:15 -0800 Subject: [PATCH 019/118] kind of working colors but not really, see video --- src/components/street-segment.js | 37 ++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 74d66ca36..82670aeb4 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -7,6 +7,15 @@ */ +var COLORS = { + red: '#ff9393', + blue: '#00b6b6', + green: '#adff83', + yellow: '#f7d117', + white: '#ffffff', + brown: '#664B00' +}; + AFRAME.registerComponent('street-segment', { schema: { preset: { @@ -41,32 +50,39 @@ AFRAME.registerComponent('street-segment', { init: function () { this.height = 0.2; // default height of segment surface box // parse preset into default surface, color - this.applyPreset(this.data.preset); + this.applyPreset(this.data.preset, false); }, - applyPreset: function (preset) { + applyPreset: function (preset, clobber = false) { // parse preset into // default surface, color const presets = { 'drive-lane': { - surface: 'asphalt' + surface: 'asphalt', + color: COLORS.white }, 'bus-lane': { - surface: 'asphalt' + surface: 'asphalt', + color: COLORS.red }, 'bike-lane': { - surface: 'asphalt' + surface: 'asphalt', + color: COLORS.green }, sidewalk: { - surface: 'sidewalk' + surface: 'sidewalk', + color: COLORS.white }, 'parking-lane': { - surface: 'concrete' + surface: 'concrete', + color: COLORS.white }, divider: { - surface: 'hatched' + surface: 'hatched', + color: COLORS.white }, grass: { - surface: 'grass' + surface: 'grass', + color: COLORS.white } }; // if preset is not found, then use default preset @@ -74,6 +90,9 @@ AFRAME.registerComponent('street-segment', { preset = 'drive-lane'; } this.el.setAttribute('street-segment', 'surface', presets[preset].surface); + if (clobber) { + this.el.setAttribute('street-segment', 'color', presets[preset].color); + } }, update: function (oldData) { const data = this.data; From 8e64775b592fbd3aad32f1841778406d483779bd Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 7 Nov 2024 23:21:35 -0800 Subject: [PATCH 020/118] remove preset logic from component --- src/aframe-streetmix-parsers.js | 55 ++++++++++++++++++++--- src/components/street-segment.js | 76 ++++---------------------------- 2 files changed, 56 insertions(+), 75 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 1a1af8d8d..e6b814a16 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -4,15 +4,47 @@ var streetmixParsersTested = require('./tested/aframe-streetmix-parsers-tested'); var { segmentVariants } = require('./segments-variants.js'); -var COLORS = { +const COLORS = { red: '#ff9393', blue: '#00b6b6', green: '#adff83', yellow: '#f7d117', + lightGray: '#dddddd', white: '#ffffff', brown: '#664B00' }; +const TYPES = { + 'drive-lane': { + surface: 'asphalt', + color: COLORS.white + }, + 'bus-lane': { + surface: 'asphalt', + color: COLORS.red + }, + 'bike-lane': { + surface: 'asphalt', + color: COLORS.green + }, + sidewalk: { + surface: 'sidewalk', + color: COLORS.white + }, + 'parking-lane': { + surface: 'concrete', + color: COLORS.lightGray + }, + divider: { + surface: 'hatched', + color: COLORS.white + }, + grass: { + surface: 'grass', + color: COLORS.white + } +}; + function cloneMixinAsChildren({ objectMixinId = '', parentEl = null, @@ -970,7 +1002,7 @@ function processSegments( var cumulativeWidthInMeters = 0; for (var i = 0; i < segments.length; i++) { - var segmentColor = COLORS.white; + var segmentColor = null; var segmentParentEl = document.createElement('a-entity'); segmentParentEl.classList.add('segment-parent-' + i); @@ -1061,7 +1093,7 @@ function processSegments( segments[i].type === 'light-rail' || segments[i].type === 'streetcar' ) { - // get the mixin id for a bus lane + // get the color for a bus lane segmentColor = getSegmentColor(variantList[1]); // get the mixin id for the vehicle (is it a trolley or a tram?) var objectMixinId = segments[i].type === 'streetcar' ? 'trolley' : 'tram'; @@ -1235,6 +1267,7 @@ function processSegments( segments[i].type === 'bus-lane' || segments[i].type === 'brt-lane' ) { + // get the color for a bus lane segmentColor = getSegmentColor(variantList[1]); segmentParentEl.append( @@ -1542,7 +1575,7 @@ function processSegments( } else if (segments[i].type === 'parking-lane') { let reusableObjectStencilsParentEl; - segmentPreset = 'bright-lane'; + segmentPreset = 'parking-lane'; let parkingMixin = 'stencils parking-t'; const carCount = 5; @@ -1626,7 +1659,7 @@ function processSegments( } // add new object if (segments[i].type !== 'separator') { - segmentParentEl.setAttribute('street-segment', 'preset', segmentPreset); + segmentParentEl.setAttribute('street-segment', 'type', segmentPreset); segmentParentEl.setAttribute( 'street-segment', 'width', @@ -1634,8 +1667,16 @@ function processSegments( ); segmentParentEl.setAttribute('street-segment', 'length', length); segmentParentEl.setAttribute('street-segment', 'elevation', elevation); - segmentParentEl.setAttribute('street-segment', 'color', segmentColor); - // segmentParentEl.setAttribute('position', { y: positionY }); + segmentParentEl.setAttribute( + 'street-segment', + 'color', + segmentColor ?? TYPES[segmentPreset]?.color + ); + segmentParentEl.setAttribute( + 'street-segment', + 'surface', + TYPES[segmentPreset]?.surface + ); } else { segmentParentEl.append( createSeparatorElement( diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 82670aeb4..73718932c 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -1,26 +1,18 @@ /* global AFRAME */ /* - - - + + + + + */ -var COLORS = { - red: '#ff9393', - blue: '#00b6b6', - green: '#adff83', - yellow: '#f7d117', - white: '#ffffff', - brown: '#664B00' -}; - AFRAME.registerComponent('street-segment', { schema: { - preset: { + type: { type: 'string', - default: 'drive-lane', oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk', 'parking-lane'] }, width: { @@ -35,7 +27,6 @@ AFRAME.registerComponent('street-segment', { }, direction: { type: 'string', - default: 'outbound', oneOf: ['inbound', 'outbound'] }, surface: { @@ -49,50 +40,6 @@ AFRAME.registerComponent('street-segment', { }, init: function () { this.height = 0.2; // default height of segment surface box - // parse preset into default surface, color - this.applyPreset(this.data.preset, false); - }, - applyPreset: function (preset, clobber = false) { - // parse preset into - // default surface, color - const presets = { - 'drive-lane': { - surface: 'asphalt', - color: COLORS.white - }, - 'bus-lane': { - surface: 'asphalt', - color: COLORS.red - }, - 'bike-lane': { - surface: 'asphalt', - color: COLORS.green - }, - sidewalk: { - surface: 'sidewalk', - color: COLORS.white - }, - 'parking-lane': { - surface: 'concrete', - color: COLORS.white - }, - divider: { - surface: 'hatched', - color: COLORS.white - }, - grass: { - surface: 'grass', - color: COLORS.white - } - }; - // if preset is not found, then use default preset - if (!presets[preset]) { - preset = 'drive-lane'; - } - this.el.setAttribute('street-segment', 'surface', presets[preset].surface); - if (clobber) { - this.el.setAttribute('street-segment', 'color', presets[preset].color); - } }, update: function (oldData) { const data = this.data; @@ -100,11 +47,6 @@ AFRAME.registerComponent('street-segment', { if (AFRAME.utils.deepEqual(oldData, data)) { return; } - // if oldData is defined AND the "preset" property has changed, then update - if (oldData.preset !== undefined && oldData.preset !== data.preset) { - this.applyPreset(data.preset, true); - return; - } this.clearMesh(); this.calculateHeight(data.elevation); this.el.object3D.position.y = this.calculateYPosition(data.elevation); // Direct Object3D manipulation @@ -179,7 +121,7 @@ AFRAME.registerComponent('street-segment', { color: ${data.color}` ); - this.el.setAttribute('shadow', 'cast: false'); + this.el.setAttribute('shadow', ''); return; }, @@ -187,7 +129,7 @@ AFRAME.registerComponent('street-segment', { // calculate the repeatCount for the material let repeatX = 0.3; // drive-lane, bus-lane, bike-lane let repeatY = length / 6; - let offsetX = 0.55; + let offsetX = 0.55; // we could get rid of this using cropped texture for asphalt if (textureSourceId === 'seamless-bright-road') { repeatX = 0.6; repeatY = 15; @@ -208,8 +150,6 @@ AFRAME.registerComponent('street-segment', { repeatY = length / 4; offsetX = 0; } - // still need to support hatched-base - // how to handle different surface materials from streetmix return [repeatX, repeatY, offsetX]; } }); From 62b482beaaf673e5a7c83649eb0ebebca7439889 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 8 Nov 2024 16:06:30 -0800 Subject: [PATCH 021/118] segment box is below --- src/aframe-streetmix-parsers.js | 7 ++++--- src/components/street-segment.js | 29 +++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index e6b814a16..537ad8bad 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -1024,8 +1024,9 @@ function processSegments( // elevation property from streetmix segment const elevation = segments[i].elevation; - const elevationLevels = [0, 0.2, 0.4]; - const elevationPosY = elevationLevels[elevation]; + // const elevationLevels = [0, 0.2, 0.4]; + // const elevationPosY = elevationLevels[elevation]; + var elevationPosY = 0; // add y elevation position as a data attribute to segment entity segmentParentEl.setAttribute('data-elevation-posY', elevationPosY); @@ -1701,7 +1702,7 @@ function processSegments( // create new brown box to represent ground underneath street const dirtBox = document.createElement('a-box'); const xPos = cumulativeWidthInMeters / 2; - dirtBox.setAttribute('position', `${xPos} -1.1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 + dirtBox.setAttribute('position', `${xPos} -1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 dirtBox.setAttribute('width', cumulativeWidthInMeters); dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 73718932c..76391db6a 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -9,6 +9,30 @@ */ +AFRAME.registerGeometry('below-box', { + schema: { + depth: { default: 1, min: 0 }, + height: { default: 1, min: 0 }, + width: { default: 1, min: 0 }, + segmentsHeight: { default: 1, min: 1, max: 20, type: 'int' }, + segmentsWidth: { default: 1, min: 1, max: 20, type: 'int' }, + segmentsDepth: { default: 1, min: 1, max: 20, type: 'int' } + }, + + init: function (data) { + this.geometry = new THREE.BoxGeometry( + data.width, + data.height, + data.depth, + data.segmentsWidth, + data.segmentsHeight, + data.segmentsDepth + ); + console.log('bro'); + this.geometry.translate(0, -data.height / 2, 0); + } +}); + AFRAME.registerComponent('street-segment', { schema: { type: { @@ -49,7 +73,8 @@ AFRAME.registerComponent('street-segment', { } this.clearMesh(); this.calculateHeight(data.elevation); - this.el.object3D.position.y = this.calculateYPosition(data.elevation); // Direct Object3D manipulation + this.tempXPosition = this.el.getAttribute('position').x; + this.el.setAttribute('position', { x: this.tempXPosition, y: this.height }); this.generateMesh(data); }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units @@ -87,7 +112,7 @@ AFRAME.registerComponent('street-segment', { // create geometry this.el.setAttribute( 'geometry', - `primitive: box; + `primitive: below-box; height: ${this.height}; depth: ${data.length}; width: ${data.width};` From f3d2209108a27f9c2f374c7ce2667c37132c4192 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 8 Nov 2024 16:13:08 -0800 Subject: [PATCH 022/118] remove unused function --- src/components/street-segment.js | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 76391db6a..4fbf648d0 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -87,19 +87,6 @@ AFRAME.registerComponent('street-segment', { this.height = heightLevels[elevation]; return; }, - calculateYPosition: function (elevation) { - let positionY; - if (elevation === 0) { - positionY = -0.1; - } else if (elevation === 2) { - positionY = 0.1; - } else if (elevation === 1) { - positionY = 0; - } else if (elevation === -1) { - positionY = -0.2; - } - return positionY; - }, clearMesh: function () { // remove the geometry from the entity this.el.removeAttribute('geometry'); From 2079e28a6f060420fbdbc34d0eff8a6a926ad6fa Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 12 Nov 2024 10:31:47 -0800 Subject: [PATCH 023/118] fix elevation for separators and cloned objects --- src/aframe-streetmix-parsers.js | 102 ++++++++++++++----------------- src/components/street-segment.js | 13 ++-- 2 files changed, 50 insertions(+), 65 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index e47e0d353..f0110bb4c 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -42,6 +42,10 @@ const TYPES = { grass: { surface: 'grass', color: COLORS.white + }, + rail: { + surface: 'asphalt', + color: COLORS.white } }; @@ -316,7 +320,6 @@ function getZPositions(start, end, step) { function createSidewalkClonedVariants( segmentWidthInMeters, density, - elevationPosY = 0, streetLength, direction = 'random', animated = false @@ -341,7 +344,6 @@ function createSidewalkClonedVariants( 10 ); const dividerParentEl = createParentElement('pedestrians-parent'); - dividerParentEl.setAttribute('position', { y: elevationPosY }); // Randomly generate avatars for (let i = 0; i < totalPedestrianNumber; i++) { const variantName = @@ -368,7 +370,6 @@ function createSidewalkClonedVariants( 1.4, streetLength, xVal, - yVal, zVal, animationDirection ); @@ -464,7 +465,6 @@ function addLinearStreetAnimation( speed, streetLength, xPos, - yVal = 0, zPos, direction ) { @@ -478,7 +478,7 @@ function addLinearStreetAnimation( property: 'position', easing: 'linear', loop: 'false', - from: { x: xPos, y: yVal, z: zPos }, + from: { x: xPos, y: 0, z: zPos }, to: { z: halfStreet }, dur: startingDuration }; @@ -486,8 +486,8 @@ function addLinearStreetAnimation( property: 'position', easing: 'linear', loop: 'true', - from: { x: xPos, y: yVal, z: -halfStreet }, - to: { x: xPos, y: yVal, z: halfStreet }, + from: { x: xPos, y: 0, z: -halfStreet }, + to: { x: xPos, y: 0, z: halfStreet }, delay: startingDuration, dur: totalStreetDuration }; @@ -539,7 +539,6 @@ function createDriveLaneElement( return createSidewalkClonedVariants( segmentWidthInMeters, 'normal', - 0, streetLength, direction, animated @@ -613,7 +612,6 @@ function createDriveLaneElement( speed, streetLength, 0, - 0, positionZ, direction ); @@ -728,7 +726,6 @@ function createOutdoorDining(length, posY) { function createMicroMobilityElement( variantList, segmentType, - posY = 0, length, showVehicles, animated = false @@ -757,7 +754,7 @@ function createMicroMobilityElement( const reusableObjectEl = document.createElement('a-entity'); const rotationY = variantList[0] === 'inbound' ? 0 : 180; reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); - reusableObjectEl.setAttribute('position', { y: posY, z: randPosZ }); + reusableObjectEl.setAttribute('position', { z: randPosZ }); if (animated) { reusableObjectEl.setAttribute('animation-mixer', ''); @@ -767,7 +764,6 @@ function createMicroMobilityElement( speed, length, 0, - posY, randPosZ, variantList[0] ); @@ -843,25 +839,23 @@ function createWayfindingElements() { function createBenchesParentElement() { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'bench-parent'); - // y = 0.2 for sidewalk elevation - placedObjectEl.setAttribute('position', '0 0.2 3.5'); + placedObjectEl.setAttribute('position', '0 0 3.5'); return placedObjectEl; } -function createBikeRacksParentElement(posY) { +function createBikeRacksParentElement() { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'bikerack-parent'); - placedObjectEl.setAttribute('position', { y: posY, z: -3.5 }); + placedObjectEl.setAttribute('position', { z: -3.5 }); return placedObjectEl; } -function createBikeShareStationElement(variantList, posY) { +function createBikeShareStationElement(variantList) { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'bikeshare'); placedObjectEl.setAttribute('mixin', 'bikeshare'); const rotationCloneY = variantList[0] === 'left' ? 90 : 270; placedObjectEl.setAttribute('rotation', '0 ' + rotationCloneY + ' 0'); - placedObjectEl.setAttribute('position', { y: posY }); return placedObjectEl; } @@ -885,16 +879,12 @@ function createParkletElement(length, variantList) { function createTreesParentElement() { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'tree-parent'); - // y = 0.2 for sidewalk elevation - placedObjectEl.setAttribute('position', '0 0.2 7'); return placedObjectEl; } function createLampsParentElement() { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'lamp-parent'); - // y = 0.2 for sidewalk elevation - placedObjectEl.setAttribute('position', '0 0.2 0'); // position="1.043 0.100 -3.463" return placedObjectEl; } @@ -926,6 +916,14 @@ function createCenteredStreetElement(segments) { return streetEl; } +function calculateHeight(elevation) { + const stepLevel = 0.15; + if (elevation <= 0) { + return stepLevel; + } + return stepLevel * (elevation + 1); +} + function createSeparatorElement( positionY, rotationY, @@ -941,7 +939,9 @@ function createSeparatorElement( segmentEl.setAttribute('rotation', '270 ' + rotationY + ' 0'); segmentEl.setAttribute('scale', scalePlane); - segmentEl.setAttribute('position', '0 ' + positionY + ' 0'); + let posY = calculateHeight(elevation) + positionY; + // take into account elevation property and add to positionY + segmentEl.setAttribute('position', '0 ' + posY + ' 0'); segmentEl.setAttribute('mixin', mixinId); if (repeatCount.length !== 0) { @@ -1025,13 +1025,6 @@ function processSegments( // elevation property from streetmix segment const elevation = segments[i].elevation; - // const elevationLevels = [0, 0.2, 0.4]; - // const elevationPosY = elevationLevels[elevation]; - var elevationPosY = 0; - - // add y elevation position as a data attribute to segment entity - segmentParentEl.setAttribute('data-elevation-posY', elevationPosY); - // Note: segment 3d models are outbound by default // If segment variant inbound, rotate segment model by 180 degrees var rotationY = @@ -1049,7 +1042,7 @@ function processSegments( if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { // make a parent entity for the stencils const stencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); // clone a bunch of stencil entities (note: this is not draw call efficient) cloneMixinAsChildren({ @@ -1067,7 +1060,7 @@ function processSegments( ) { // make a parent entity for the stencils const stencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); // get the mixin id for a bike lane segmentColor = getSegmentColor(variantList[1]); @@ -1085,7 +1078,6 @@ function processSegments( createMicroMobilityElement( variantList, segments[i].type, - elevationPosY, length, showVehicles, globalAnimated @@ -1095,6 +1087,7 @@ function processSegments( segments[i].type === 'light-rail' || segments[i].type === 'streetcar' ) { + segmentPreset = 'rail'; // get the color for a bus lane segmentColor = getSegmentColor(variantList[1]); // get the mixin id for the vehicle (is it a trolley or a tram?) @@ -1127,7 +1120,7 @@ function processSegments( // make the parent for all the objects to be cloned const stencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: mixinString, @@ -1141,7 +1134,7 @@ function processSegments( if (variantList[1] === 'shared') { // add an additional marking to represent the opposite turn marking stencil (rotated 180º) const stencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: -3 * isOutbound }); cloneMixinAsChildren({ @@ -1280,7 +1273,7 @@ function processSegments( let reusableObjectStencilsParentEl; reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: 'stencils word-bus', @@ -1293,7 +1286,7 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: 10 }); cloneMixinAsChildren({ @@ -1307,7 +1300,7 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: 20 }); cloneMixinAsChildren({ @@ -1346,7 +1339,7 @@ function processSegments( let reusableObjectStencilsParentEl; reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: 5 }); cloneMixinAsChildren({ @@ -1360,7 +1353,7 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: -5 }); cloneMixinAsChildren({ @@ -1379,7 +1372,6 @@ function processSegments( createSidewalkClonedVariants( segmentWidthInMeters, variantList[0], - elevationPosY, length, 'random', isAnimated @@ -1414,7 +1406,7 @@ function processSegments( } } else if (segments[i].type === 'sidewalk-bike-rack') { // make the parent for all the bike racks - const bikeRacksParentEl = createBikeRacksParentElement(elevationPosY); + const bikeRacksParentEl = createBikeRacksParentElement(0); const rotationCloneY = variantList[1] === 'sidewalk-parallel' ? 90 : 0; cloneMixinAsChildren({ @@ -1430,15 +1422,13 @@ function processSegments( segmentParentEl.append(createMagicCarpetElement(showVehicles)); } else if (segments[i].type === 'outdoor-dining') { segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; - segmentParentEl.append(createOutdoorDining(length, elevationPosY)); + segmentParentEl.append(createOutdoorDining(length, 0)); } else if (segments[i].type === 'parklet') { segmentPreset = 'drive-lane'; segmentParentEl.append(createParkletElement(length, variantList)); } else if (segments[i].type === 'bikeshare') { // make the parent for all the stations - segmentParentEl.append( - createBikeShareStationElement(variantList, elevationPosY) - ); + segmentParentEl.append(createBikeShareStationElement(variantList, 0)); } else if (segments[i].type === 'utilities') { var rotation = variantList[0] === 'right' ? '0 180 0' : '0 0 0'; const utilityPoleElems = createClonedVariants( @@ -1527,9 +1517,7 @@ function processSegments( segmentParentEl.append(lampsParentEl); } else if (segments[i].type === 'transit-shelter') { var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; - segmentParentEl.append( - createBusStopElement(rotationBusStopY, elevationPosY) - ); + segmentParentEl.append(createBusStopElement(rotationBusStopY, 0)); } else if (segments[i].type === 'brt-station') { segmentParentEl.append(createBrtStationElement()); } else if ( @@ -1537,25 +1525,25 @@ function processSegments( variantList[0] === 'dashed' ) { segmentPreset = 'markings dashed-stripe'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + positionY = 0.01; // make sure the lane marker is above the asphalt // for all markings material property repeat = "1 25". So every 150/25=6 meters put a dash repeatCount[0] = 1; repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'separator' && variantList[0] === 'solid') { segmentPreset = 'markings solid-stripe'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + positionY = 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'doubleyellow' ) { segmentPreset = 'markings solid-doubleyellow'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + positionY = 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'shortdashedyellow' ) { segmentPreset = 'markings yellow short-dashed-stripe'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + positionY = 0.01; // make sure the lane marker is above the asphalt // for short-dashed-stripe every 3 meters put a dash repeatCount[0] = 1; repeatCount[1] = parseInt(length / 3); @@ -1564,13 +1552,13 @@ function processSegments( variantList[0] === 'soliddashedyellow' ) { segmentPreset = 'markings yellow solid-dashed'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + positionY = 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellowinverted' ) { segmentPreset = 'markings yellow solid-dashed'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + positionY = 0.01; // make sure the lane marker is above the asphalt rotationY = '180'; repeatCount[0] = 1; repeatCount[1] = parseInt(length / 6); @@ -1626,7 +1614,7 @@ function processSegments( ); if (variantList[1] === 'left') { reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: parkingMixin, @@ -1639,7 +1627,7 @@ function processSegments( }); } else { reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: parkingMixin, diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 4fbf648d0..3ed43dc97 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -28,7 +28,6 @@ AFRAME.registerGeometry('below-box', { data.segmentsHeight, data.segmentsDepth ); - console.log('bro'); this.geometry.translate(0, -data.height / 2, 0); } }); @@ -72,20 +71,18 @@ AFRAME.registerComponent('street-segment', { return; } this.clearMesh(); - this.calculateHeight(data.elevation); + this.height = this.calculateHeight(data.elevation); this.tempXPosition = this.el.getAttribute('position').x; this.el.setAttribute('position', { x: this.tempXPosition, y: this.height }); this.generateMesh(data); }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units calculateHeight: function (elevation) { - const heightLevels = [0.2, 0.4, 0.6]; - if (elevation === -1) { - this.height = 0.2; - return; + const stepLevel = 0.15; + if (elevation <= 0) { + return stepLevel; } - this.height = heightLevels[elevation]; - return; + return stepLevel * (elevation + 1); }, clearMesh: function () { // remove the geometry from the entity From 68db024520047143aa03695a54992cc1f6a2474d Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 16 Nov 2024 21:05:54 -0800 Subject: [PATCH 024/118] add solid and none support --- src/components/street-segment.js | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 3ed43dc97..288d12ce1 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -34,10 +34,6 @@ AFRAME.registerGeometry('below-box', { AFRAME.registerComponent('street-segment', { schema: { - type: { - type: 'string', - oneOf: ['drive-lane', 'bus-lane', 'bike-lane', 'sidewalk', 'parking-lane'] - }, width: { type: 'number' }, @@ -55,7 +51,16 @@ AFRAME.registerComponent('street-segment', { surface: { type: 'string', default: 'asphalt', - oneOf: ['asphalt', 'concrete', 'grass', 'sidewalk', 'gravel', 'sand'] + oneOf: [ + 'asphalt', + 'concrete', + 'grass', + 'sidewalk', + 'gravel', + 'sand', + 'none', + 'solid' + ] }, color: { type: 'color' @@ -110,7 +115,9 @@ AFRAME.registerComponent('street-segment', { sidewalk: 'seamless-sidewalk', gravel: 'compacted-gravel-texture', sand: 'sandy-asphalt-texture', - hatched: 'hatched-base' + hatched: 'hatched-base', + none: 'none', + solid: '' }; let textureSourceId = textureMaps[data.surface]; @@ -132,6 +139,12 @@ AFRAME.registerComponent('street-segment', { this.el.setAttribute('shadow', ''); + this.el.setAttribute( + 'material', + 'visible', + textureMaps[data.surface] !== 'none' + ); + return; }, calculateTextureRepeat: function (length, width, textureSourceId) { From f98e116e4faa185bea52e8c1104067ab3354fca2 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 18 Nov 2024 14:03:12 -0800 Subject: [PATCH 025/118] street-generated-fixed basic working --- src/aframe-streetmix-parsers.js | 91 ++++++++---------------- src/components/street-generated-fixed.js | 82 +++++++++++++++++++++ src/index.js | 1 + 3 files changed, 114 insertions(+), 60 deletions(-) create mode 100644 src/components/street-generated-fixed.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index f0110bb4c..af2de3121 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -271,17 +271,6 @@ function createParentElement(className) { return parentEl; } -function createDividerVariant(variantName, clonedObjectRadius, step = 2.25) { - const dividerParentEl = createParentElement(`dividers-${variantName}-parent`); - cloneMixinAsChildren({ - objectMixinId: `dividers-${variantName}`, - parentEl: dividerParentEl, - step: step, - radius: clonedObjectRadius - }); - return dividerParentEl; -} - function createClonedVariants( variantName, clonedObjectRadius, @@ -876,12 +865,6 @@ function createParkletElement(length, variantList) { return parkletParent; } -function createTreesParentElement() { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'tree-parent'); - return placedObjectEl; -} - function createLampsParentElement() { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'lamp-parent'); @@ -1161,60 +1144,57 @@ function processSegments( segmentParentEl.append(bollardsParentEl); } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { segmentPreset = 'grass'; - segmentParentEl.append( - createDividerVariant('flowers', clonedObjectRadius, 2.25) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-flowers; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'divider' && variantList[0] === 'planting-strip' ) { segmentPreset = 'grass'; - segmentParentEl.append( - createDividerVariant('planting-strip', clonedObjectRadius, 2.25) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-planting-strip; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'divider' && variantList[0] === 'planter-box' ) { segmentPreset = 'grass'; - segmentParentEl.append( - createDividerVariant('planter-box', clonedObjectRadius, 2.45) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-planter-box; spacing: 2.45; length: ${length}` ); } else if ( segments[i].type === 'divider' && variantList[0] === 'palm-tree' ) { segmentPreset = 'grass'; - const treesParentEl = createTreesParentElement(); - cloneMixinAsChildren({ - objectMixinId: 'palm-tree', - parentEl: treesParentEl, - randomY: true, - radius: clonedObjectRadius - }); - segmentParentEl.append(treesParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: palm-tree; length: ${length}` + ); } else if ( segments[i].type === 'divider' && variantList[0] === 'big-tree' ) { segmentPreset = 'grass'; - const treesParentEl = createTreesParentElement(); - cloneMixinAsChildren({ - objectMixinId: 'tree3', - parentEl: treesParentEl, - randomY: true, - radius: clonedObjectRadius - }); - segmentParentEl.append(treesParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: tree3; length: ${length}` + ); } else if (segments[i].type === 'divider' && variantList[0] === 'bush') { segmentPreset = 'grass'; - segmentParentEl.append( - createDividerVariant('bush', clonedObjectRadius, 2.25) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-bush; spacing: 2.25; length: ${length}` ); } else if (segments[i].type === 'divider' && variantList[0] === 'dome') { segmentPreset = 'divider'; - segmentParentEl.append( - createDividerVariant('dome', clonedObjectRadius, 2.25) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-dome; spacing: 2.25; length: ${length}` ); } else if (segments[i].type === 'divider') { segmentPreset = 'divider'; @@ -1430,30 +1410,21 @@ function processSegments( // make the parent for all the stations segmentParentEl.append(createBikeShareStationElement(variantList, 0)); } else if (segments[i].type === 'utilities') { - var rotation = variantList[0] === 'right' ? '0 180 0' : '0 0 0'; - const utilityPoleElems = createClonedVariants( - 'utility_pole', - clonedObjectRadius, - 15, - rotation + var rotation = variantList[0] === 'right' ? '180' : '0'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: utility_pole; length: ${length}; offset: 0.25; facing: ${rotation}` ); - segmentParentEl.append(utilityPoleElems); } else if (segments[i].type === 'sidewalk-tree') { - // make the parent for all the trees - const treesParentEl = createTreesParentElement(); if (variantList[0] === 'palm-tree') { objectMixinId = 'palm-tree'; } else { objectMixinId = 'tree3'; } - // clone a bunch of trees under the parent - cloneMixinAsChildren({ - objectMixinId: objectMixinId, - parentEl: treesParentEl, - randomY: true, - radius: clonedObjectRadius - }); - segmentParentEl.append(treesParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: ${objectMixinId}; length: ${length}; randomFacing: true;` + ); } else if ( segments[i].type === 'sidewalk-lamp' && (variantList[1] === 'modern' || variantList[1] === 'pride') diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js new file mode 100644 index 000000000..8b2e642fd --- /dev/null +++ b/src/components/street-generated-fixed.js @@ -0,0 +1,82 @@ +/* global AFRAME */ + +// a-frame component to generate cloned models along a street +// this moves logic from aframe-streetmix-parsers into this component + +AFRAME.registerComponent('street-generated-fixed', { + schema: { + model: { + type: 'string' + }, + length: { + // length in meters of linear path to fill with clones + type: 'number' + }, + spacing: { + // spacing in meters between clones + default: 15, + type: 'number' + }, + offset: { + // z (inbound/outbound) offset as a fraction of spacing value + default: 0.5, // this is used to place different models at different z-levels with the same spacing value + type: 'number' + }, + facing: { + default: 0, // this is a Y Rotation value in degrees + type: 'number' + }, + randomFacing: { + default: false, + type: 'boolean' + } + // seed: { // seed not yet supported + // default: 0, + // type: 'number' + // } + }, + init: function () { + this.createdEntities = []; + }, + update: function (oldData) { + // generate a function that creates a cloned set of x entities based on spacing and length values from the model shortname gltf file loaded in aframe + const data = this.data; + // if oldData is same as current data, then don't update + if (AFRAME.utils.deepEqual(oldData, data)) { + return; + } + + // For each clone in this.entities, remove it + this.createdEntities.forEach((entity) => { + entity.remove(); + }); + this.createdEntities = []; + + // Calculate number of clones needed based on length and spacing + const numClones = Math.floor(data.length / data.spacing); + + // Create clones and position them along the length + for (let i = 0; i < numClones; i++) { + const clone = document.createElement('a-entity'); + clone.setAttribute('mixin', data.model); + + console.log('clone', clone); + // Position each clone evenly spaced along z-axis + // offset default is 0.5 so that clones don't start exactly at street start which looks weird + const zPosition = (i + data.offset) * data.spacing - data.length / 2; + clone.setAttribute('position', `0 0 ${zPosition}`); + + if (data.randomFacing) { + clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); + } else { + clone.setAttribute('rotation', `0 ${data.facing} 0`); + } + clone.classList.add('autocreated'); + // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings + clone.setAttribute('data-no-transform', ''); + + this.el.appendChild(clone); + this.createdEntities.push(clone); + } + } +}); diff --git a/src/index.js b/src/index.js index 5477f3a64..b7153921c 100644 --- a/src/index.js +++ b/src/index.js @@ -22,6 +22,7 @@ require('./components/street-environment.js'); require('./components/intersection.js'); require('./components/obb-clipping.js'); require('./components/street-segment.js'); +require('./components/street-generated-fixed.js'); require('./editor/index.js'); const state = useStore.getState(); From 93de88173f0bdee82cb30434afeec54d4ff138d3 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 18 Nov 2024 14:43:17 -0800 Subject: [PATCH 026/118] benches, bikeracks, barricades, and bollards --- src/aframe-streetmix-parsers.js | 119 ++++++++------------------------ 1 file changed, 28 insertions(+), 91 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index af2de3121..d0db4c01c 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -259,35 +259,12 @@ function createTracksParentElement(length, objectMixinId) { return placedObjectEl; } -function createBollardsParentElement() { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'bollard-parent'); - return placedObjectEl; -} - function createParentElement(className) { const parentEl = document.createElement('a-entity'); parentEl.setAttribute('class', className); return parentEl; } -function createClonedVariants( - variantName, - clonedObjectRadius, - step = 2.25, - rotation = '0 0 0' -) { - const dividerParentEl = createParentElement(`${variantName}-parent`); - cloneMixinAsChildren({ - objectMixinId: variantName, - parentEl: dividerParentEl, - step: step, - radius: clonedObjectRadius, - rotation: rotation - }); - return dividerParentEl; -} - function getRandomIntInclusive(min, max) { min = Math.ceil(min); max = Math.floor(max); @@ -825,20 +802,6 @@ function createWayfindingElements() { return wayfindingParentEl; } -function createBenchesParentElement() { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'bench-parent'); - placedObjectEl.setAttribute('position', '0 0 3.5'); - return placedObjectEl; -} - -function createBikeRacksParentElement() { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'bikerack-parent'); - placedObjectEl.setAttribute('position', { z: -3.5 }); - return placedObjectEl; -} - function createBikeShareStationElement(variantList) { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'bikeshare'); @@ -1133,15 +1096,10 @@ function processSegments( } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { segmentPreset = 'divider'; // make some bollards - const bollardsParentEl = createBollardsParentElement(); - cloneMixinAsChildren({ - objectMixinId: 'bollard', - parentEl: bollardsParentEl, - step: 4, - radius: clonedObjectRadius - }); - // add the bollards to the segment parent - segmentParentEl.append(bollardsParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: bollard; spacing: 4; length: ${length}` + ); } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { segmentPreset = 'grass'; segmentParentEl.setAttribute( @@ -1203,40 +1161,36 @@ function processSegments( variantList[0] === 'barricade' ) { segmentPreset = 'drive-lane'; - segmentParentEl.append( - createClonedVariants('temporary-barricade', clonedObjectRadius, 2.25) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: temporary-barricade; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'traffic-cone' ) { segmentPreset = 'drive-lane'; - segmentParentEl.append( - createClonedVariants('temporary-traffic-cone', clonedObjectRadius, 2.25) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: temporary-traffic-cone; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'jersey-barrier-plastic' ) { segmentPreset = 'drive-lane'; - segmentParentEl.append( - createClonedVariants( - 'temporary-jersey-barrier-plastic', - clonedObjectRadius, - 2.25 - ) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: jersey-barrier-plastic; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'jersey-barrier-concrete' ) { segmentPreset = 'drive-lane'; - segmentParentEl.append( - createClonedVariants( - 'temporary-jersey-barrier-concrete', - clonedObjectRadius, - 2.93 - ) + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: temporary-jersey-barrier-concrete; spacing: 2.93; length: ${length}` ); } else if ( segments[i].type === 'bus-lane' || @@ -1360,43 +1314,26 @@ function processSegments( } else if (segments[i].type === 'sidewalk-wayfinding') { segmentParentEl.append(createWayfindingElements()); } else if (segments[i].type === 'sidewalk-bench') { - // make the parent for all the benches - const benchesParentEl = createBenchesParentElement(); - const rotationCloneY = variantList[0] === 'right' ? -90 : 90; if (variantList[0] === 'center') { - cloneMixinAsChildren({ - objectMixinId: 'bench_orientation_center', - parentEl: benchesParentEl, - rotation: '0 ' + rotationCloneY + ' 0', - radius: clonedObjectRadius - }); - // add benches to the segment parent - segmentParentEl.append(benchesParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: bench_orientation_center; length: ${length}; facing: ${rotationCloneY}; offset: 0.1` + ); } else { // `right` or `left` bench - cloneMixinAsChildren({ - objectMixinId: 'bench', - parentEl: benchesParentEl, - rotation: '0 ' + rotationCloneY + ' 0', - radius: clonedObjectRadius - }); - // add benches to the segment parent - segmentParentEl.append(benchesParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: bench; length: ${length}; facing: ${rotationCloneY}; offset: 0.1` + ); } } else if (segments[i].type === 'sidewalk-bike-rack') { - // make the parent for all the bike racks - const bikeRacksParentEl = createBikeRacksParentElement(0); - const rotationCloneY = variantList[1] === 'sidewalk-parallel' ? 90 : 0; - cloneMixinAsChildren({ - objectMixinId: 'bikerack', - parentEl: bikeRacksParentEl, - rotation: '0 ' + rotationCloneY + ' 0', - radius: clonedObjectRadius - }); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: bikerack; length: ${length}; facing: ${rotationCloneY}; offset: 0.2` + ); // add bike racks to the segment parent - segmentParentEl.append(bikeRacksParentEl); } else if (segments[i].type === 'magic-carpet') { segmentPreset = 'drive-lane'; segmentParentEl.append(createMagicCarpetElement(showVehicles)); From 542cee094a971175ed5e3bd4e44c65b0cf30f118 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 18 Nov 2024 15:08:55 -0800 Subject: [PATCH 027/118] minimum spacing, add clone label --- src/components/street-generated-fixed.js | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js index 8b2e642fd..bda8c1f84 100644 --- a/src/components/street-generated-fixed.js +++ b/src/components/street-generated-fixed.js @@ -23,10 +23,11 @@ AFRAME.registerComponent('street-generated-fixed', { type: 'number' }, facing: { - default: 0, // this is a Y Rotation value in degrees + default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 type: 'number' }, randomFacing: { + // if true, facing is ignored default: false, type: 'boolean' } @@ -52,8 +53,10 @@ AFRAME.registerComponent('street-generated-fixed', { }); this.createdEntities = []; + this.correctedSpacing = data.spacing < 1 ? 1 : data.spacing; // return 1 if data.spacing is less than 1 + // Calculate number of clones needed based on length and spacing - const numClones = Math.floor(data.length / data.spacing); + const numClones = Math.floor(data.length / this.correctedSpacing); // Create clones and position them along the length for (let i = 0; i < numClones; i++) { @@ -63,7 +66,8 @@ AFRAME.registerComponent('street-generated-fixed', { console.log('clone', clone); // Position each clone evenly spaced along z-axis // offset default is 0.5 so that clones don't start exactly at street start which looks weird - const zPosition = (i + data.offset) * data.spacing - data.length / 2; + const zPosition = + (i + data.offset) * this.correctedSpacing - data.length / 2; clone.setAttribute('position', `0 0 ${zPosition}`); if (data.randomFacing) { @@ -74,6 +78,7 @@ AFRAME.registerComponent('street-generated-fixed', { clone.classList.add('autocreated'); // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); this.el.appendChild(clone); this.createdEntities.push(clone); From 8c2a4afca7b29722f338ccb487229149c6b03fa4 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 18 Nov 2024 15:38:09 -0800 Subject: [PATCH 028/118] migrate lamps --- src/aframe-streetmix-parsers.js | 72 ++++++++---------------- src/components/street-generated-fixed.js | 27 ++++++--- 2 files changed, 44 insertions(+), 55 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index d0db4c01c..7d70294b5 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -828,12 +828,6 @@ function createParkletElement(length, variantList) { return parkletParent; } -function createLampsParentElement() { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'lamp-parent'); - return placedObjectEl; -} - function createBusStopElement(rotationBusStopY, posY) { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'bus-stop'); @@ -1318,20 +1312,20 @@ function processSegments( if (variantList[0] === 'center') { segmentParentEl.setAttribute( 'street-generated-fixed', - `model: bench_orientation_center; length: ${length}; facing: ${rotationCloneY}; offset: 0.1` + `model: bench_orientation_center; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` ); } else { // `right` or `left` bench segmentParentEl.setAttribute( 'street-generated-fixed', - `model: bench; length: ${length}; facing: ${rotationCloneY}; offset: 0.1` + `model: bench; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` ); } } else if (segments[i].type === 'sidewalk-bike-rack') { const rotationCloneY = variantList[1] === 'sidewalk-parallel' ? 90 : 0; segmentParentEl.setAttribute( 'street-generated-fixed', - `model: bikerack; length: ${length}; facing: ${rotationCloneY}; offset: 0.2` + `model: bikerack; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.2` ); // add bike racks to the segment parent } else if (segments[i].type === 'magic-carpet') { @@ -1350,7 +1344,7 @@ function processSegments( var rotation = variantList[0] === 'right' ? '180' : '0'; segmentParentEl.setAttribute( 'street-generated-fixed', - `model: utility_pole; length: ${length}; offset: 0.25; facing: ${rotation}` + `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotation}` ); } else if (segments[i].type === 'sidewalk-tree') { if (variantList[0] === 'palm-tree') { @@ -1366,63 +1360,45 @@ function processSegments( segments[i].type === 'sidewalk-lamp' && (variantList[1] === 'modern' || variantList[1] === 'pride') ) { - // Make the parent object for all the lamps - const lampsParentEl = createLampsParentElement(); if (variantList[0] === 'both') { - cloneMixinAsChildren({ - objectMixinId: 'lamp-modern-double', - parentEl: lampsParentEl, - rotation: '0 0 0', - radius: clonedObjectRadius - }); - segmentParentEl.append(lampsParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: lamp-modern-double; length: ${length}; cycleOffset: 0.4;` + ); } else { var rotationCloneY = variantList[0] === 'right' ? 0 : 180; - cloneMixinAsChildren({ - objectMixinId: 'lamp-modern', - parentEl: lampsParentEl, - rotation: '0 ' + rotationCloneY + ' 0', - radius: clonedObjectRadius - }); - segmentParentEl.append(lampsParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: lamp-modern; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.4;` + ); } // Add the pride flags to the lamp posts if ( variantList[1] === 'pride' && (variantList[0] === 'right' || variantList[0] === 'both') ) { - cloneMixinAsChildren({ - objectMixinId: 'pride-flag', - parentEl: lampsParentEl, - positionXYString: '0.409 5', - radius: clonedObjectRadius - }); + segmentParentEl.setAttribute( + 'street-generated-fixed__pride-flag', + `model: pride-flag; length: ${length}; cycleOffset: 0.4; positionX: 0.409; positionY: 5;` + ); } if ( variantList[1] === 'pride' && (variantList[0] === 'left' || variantList[0] === 'both') ) { - cloneMixinAsChildren({ - objectMixinId: 'pride-flag', - parentEl: lampsParentEl, - rotation: '0 -180 0', - positionXYString: '-0.409 5', - radius: clonedObjectRadius - }); + segmentParentEl.setAttribute( + 'street-generated-fixed__pride-flag', + `model: pride-flag; length: ${length}; facing: 180; cycleOffset: 0.4; positionX: -0.409; positionY: 5;` + ); } } else if ( segments[i].type === 'sidewalk-lamp' && variantList[1] === 'traditional' ) { - // make the parent for all the lamps - const lampsParentEl = createLampsParentElement(); - // clone a bunch of lamps under the parent - cloneMixinAsChildren({ - objectMixinId: 'lamp-traditional', - parentEl: lampsParentEl, - radius: clonedObjectRadius - }); - segmentParentEl.append(lampsParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: lamp-traditional; length: ${length};` + ); } else if (segments[i].type === 'transit-shelter') { var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; segmentParentEl.append(createBusStopElement(rotationBusStopY, 0)); diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js index bda8c1f84..020fc9986 100644 --- a/src/components/street-generated-fixed.js +++ b/src/components/street-generated-fixed.js @@ -4,6 +4,7 @@ // this moves logic from aframe-streetmix-parsers into this component AFRAME.registerComponent('street-generated-fixed', { + multiple: true, schema: { model: { type: 'string' @@ -17,7 +18,17 @@ AFRAME.registerComponent('street-generated-fixed', { default: 15, type: 'number' }, - offset: { + positionX: { + // x position of clones along the length + default: 0, + type: 'number' + }, + positionY: { + // y position of clones along the length + default: 0, + type: 'number' + }, + cycleOffset: { // z (inbound/outbound) offset as a fraction of spacing value default: 0.5, // this is used to place different models at different z-levels with the same spacing value type: 'number' @@ -27,7 +38,7 @@ AFRAME.registerComponent('street-generated-fixed', { type: 'number' }, randomFacing: { - // if true, facing is ignored + // if true, facing is ignored and a random Y Rotation is applied to each clone default: false, type: 'boolean' } @@ -62,13 +73,15 @@ AFRAME.registerComponent('street-generated-fixed', { for (let i = 0; i < numClones; i++) { const clone = document.createElement('a-entity'); clone.setAttribute('mixin', data.model); - - console.log('clone', clone); // Position each clone evenly spaced along z-axis // offset default is 0.5 so that clones don't start exactly at street start which looks weird - const zPosition = - (i + data.offset) * this.correctedSpacing - data.length / 2; - clone.setAttribute('position', `0 0 ${zPosition}`); + const positionZ = + data.length / 2 - (i + data.cycleOffset) * this.correctedSpacing; + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: positionZ + }); if (data.randomFacing) { clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); From 8afd69ce6ee3e8e4f862235221c239829dca6656 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 18 Nov 2024 15:56:04 -0800 Subject: [PATCH 029/118] seawall and fence clones --- src/aframe-streetmix-parsers.js | 43 +++++++++++---------------------- 1 file changed, 14 insertions(+), 29 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 7d70294b5..769033d2b 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -235,7 +235,7 @@ function createRailsElement(length, railsPosX) { }; placedObjectEl.setAttribute('geometry', railsGeometry); placedObjectEl.setAttribute('material', railsMaterial); - placedObjectEl.setAttribute('class', 'rails'); + placedObjectEl.setAttribute('data-layer-name', 'rails'); placedObjectEl.setAttribute('shadow', 'receive:true; cast: true'); placedObjectEl.setAttribute('position', railsPosX + ' 0.2 0'); // position="1.043 0.100 -3.463" @@ -244,7 +244,7 @@ function createRailsElement(length, railsPosX) { function createTracksParentElement(length, objectMixinId) { const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'track-parent'); + placedObjectEl.setAttribute('data-layer-name', 'Tracks Parent'); placedObjectEl.setAttribute('position', '0 -0.2 0'); // position="1.043 0.100 -3.463" // add rails const railsWidth = { @@ -259,12 +259,6 @@ function createTracksParentElement(length, objectMixinId) { return placedObjectEl; } -function createParentElement(className) { - const parentEl = document.createElement('a-entity'); - parentEl.setAttribute('class', className); - return parentEl; -} - function getRandomIntInclusive(min, max) { min = Math.ceil(min); max = Math.floor(max); @@ -309,7 +303,8 @@ function createSidewalkClonedVariants( densityFactors[density] * streetLength, 10 ); - const dividerParentEl = createParentElement('pedestrians-parent'); + const dividerParentEl = document.createElement('a-entity'); + dividerParentEl.setAttribute('data-layer-name', 'Pedestrians Parent'); // Randomly generate avatars for (let i = 0; i < totalPedestrianNumber; i++) { const variantName = @@ -1589,7 +1584,6 @@ module.exports.processSegments = processSegments; // test - for streetObject of street 44 and buildingElementId render 2 building sides function processBuildings(left, right, streetWidth, showGround, length) { const buildingElement = document.createElement('a-entity'); - const clonedObjectRadius = 0.45 * length; buildingElement.classList.add('buildings-parent'); buildingElement.setAttribute( 'data-layer-name', @@ -1720,7 +1714,6 @@ function processBuildings(left, right, streetWidth, showGround, length) { if (currentValue === 'waterfront' || currentValue === 'compound-wall') { const objectPositionX = buildingPositionX - (sideMultiplier * 150) / 2; const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'seawall-parent'); placedObjectEl.setAttribute('position', { x: objectPositionX, z: 4.5 }); // position="1.043 0.100 -3.463" let rotationCloneY; if (currentValue === 'compound-wall') { @@ -1732,34 +1725,26 @@ function processBuildings(left, right, streetWidth, showGround, length) { } else { rotationCloneY = side === 'left' ? -90 : 90; } - placedObjectEl.classList.add('seawall-parent-' + side); + placedObjectEl.setAttribute('data-layer-name', 'seawall-parent-' + side); + placedObjectEl.setAttribute( + 'street-generated-fixed', + `model: seawall; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.8;` + ); buildingElement.appendChild(placedObjectEl); - // clone a bunch of seawalls under the parent - cloneMixinAsChildren({ - objectMixinId: 'seawall', - parentEl: placedObjectEl, - rotation: '0 ' + rotationCloneY + ' 0', - step: 15, - radius: clonedObjectRadius - }); } if (currentValue === 'fence' || currentValue === 'parking-lot') { const objectPositionX = buildingPositionX - (sideMultiplier * 150) / 2; // make the parent for all the objects to be cloned const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'fence-parent'); placedObjectEl.setAttribute('position', objectPositionX + ' 0 4.625'); // position="1.043 0.100 -3.463" - placedObjectEl.classList.add('fence-parent-' + buildingPositionX); + placedObjectEl.setAttribute('data-layer-name', 'fence-parent'); // clone a bunch of fences under the parent const rotationCloneY = side === 'right' ? -90 : 90; - cloneMixinAsChildren({ - objectMixinId: 'fence', - parentEl: placedObjectEl, - rotation: '0 ' + rotationCloneY + ' 0', - step: 9.25, - radius: clonedObjectRadius - }); + placedObjectEl.setAttribute( + 'street-generated-fixed', + `model: fence; length: ${length}; spacing: 9.25; facing: ${rotationCloneY}; cycleOffset: 1` + ); buildingElement.appendChild(placedObjectEl); } }); From 5dd1f505c06f1d77df26abbe71bb954c9cb59a10 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 18 Nov 2024 20:50:00 -0800 Subject: [PATCH 030/118] bike share, bus stop --- src/aframe-streetmix-parsers.js | 30 +++----- src/components/street-generated-single.js | 90 +++++++++++++++++++++++ src/index.js | 1 + 3 files changed, 100 insertions(+), 21 deletions(-) create mode 100644 src/components/street-generated-single.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 769033d2b..6577192cd 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -797,15 +797,6 @@ function createWayfindingElements() { return wayfindingParentEl; } -function createBikeShareStationElement(variantList) { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'bikeshare'); - placedObjectEl.setAttribute('mixin', 'bikeshare'); - const rotationCloneY = variantList[0] === 'left' ? 90 : 270; - placedObjectEl.setAttribute('rotation', '0 ' + rotationCloneY + ' 0'); - return placedObjectEl; -} - function createParkletElement(length, variantList) { const parkletParent = document.createElement('a-entity'); const parkletLength = 4.03; @@ -823,15 +814,6 @@ function createParkletElement(length, variantList) { return parkletParent; } -function createBusStopElement(rotationBusStopY, posY) { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'bus-stop'); - placedObjectEl.setAttribute('rotation', '0 ' + rotationBusStopY + ' 0'); - placedObjectEl.setAttribute('mixin', 'bus-stop'); - placedObjectEl.setAttribute('position', { y: posY }); - return placedObjectEl; -} - function createBrtStationElement() { const placedObjectEl = document.createElement('a-entity'); placedObjectEl.setAttribute('class', 'brt-station'); @@ -1333,8 +1315,11 @@ function processSegments( segmentPreset = 'drive-lane'; segmentParentEl.append(createParkletElement(length, variantList)); } else if (segments[i].type === 'bikeshare') { - // make the parent for all the stations - segmentParentEl.append(createBikeShareStationElement(variantList, 0)); + const rotationCloneY = variantList[0] === 'left' ? 90 : 270; + segmentParentEl.setAttribute( + 'street-generated-single', + `model: bikeshare; length: ${length}; facing: ${rotationCloneY}; justify: middle;` + ); } else if (segments[i].type === 'utilities') { var rotation = variantList[0] === 'right' ? '180' : '0'; segmentParentEl.setAttribute( @@ -1396,7 +1381,10 @@ function processSegments( ); } else if (segments[i].type === 'transit-shelter') { var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; - segmentParentEl.append(createBusStopElement(rotationBusStopY, 0)); + segmentParentEl.setAttribute( + 'street-generated-single', + `model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` + ); } else if (segments[i].type === 'brt-station') { segmentParentEl.append(createBrtStationElement()); } else if ( diff --git a/src/components/street-generated-single.js b/src/components/street-generated-single.js new file mode 100644 index 000000000..9e970eacc --- /dev/null +++ b/src/components/street-generated-single.js @@ -0,0 +1,90 @@ +/* global AFRAME */ + +// a-frame component to one cloned model along a street +// this moves logic from aframe-streetmix-parsers into this component + +AFRAME.registerComponent('street-generated-single', { + multiple: true, + schema: { + model: { + type: 'string' + }, + length: { + // length in meters of segment + type: 'number' + }, + justify: { + default: 'middle', + oneOf: ['start', 'middle', 'end'] + }, + padding: { + // spacing in meters between segment edge and model + default: 4, + type: 'number' + }, + positionX: { + // x position of model + default: 0, + type: 'number' + }, + positionY: { + // y position of model + default: 0, + type: 'number' + }, + facing: { + default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 + type: 'number' + }, + randomFacing: { + // if true, facing is ignored and a random Y Rotation is applied to each clone + default: false, + type: 'boolean' + } + }, + init: function () { + this.createdEntities = []; + }, + update: function (oldData) { + const data = this.data; + // if oldData is same as current data, then don't update + if (AFRAME.utils.deepEqual(oldData, data)) { + return; + } + + // For each clone in this.entities, remove it + this.createdEntities.forEach((entity) => { + entity.remove(); + }); + this.createdEntities = []; + + const clone = document.createElement('a-entity'); + clone.setAttribute('mixin', data.model); + + // Position z is dependent upon length and padding + let positionZ = 0; // middle + if (data.justify === 'start') { + positionZ = data.length / 2 - data.padding; + } else if (data.justify === 'end') { + positionZ = -data.length / 2 + data.padding; + } + + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: positionZ + }); + + if (data.randomFacing) { + clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); + } else { + clone.setAttribute('rotation', `0 ${data.facing} 0`); + } + clone.classList.add('autocreated'); + // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings + // clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); + this.el.appendChild(clone); + this.createdEntities.push(clone); + } +}); diff --git a/src/index.js b/src/index.js index b7153921c..09fc6b21b 100644 --- a/src/index.js +++ b/src/index.js @@ -23,6 +23,7 @@ require('./components/intersection.js'); require('./components/obb-clipping.js'); require('./components/street-segment.js'); require('./components/street-generated-fixed.js'); +require('./components/street-generated-single.js'); require('./editor/index.js'); const state = useStore.getState(); From 9161c92b0e1c66828d910ee192b01463bdf888b8 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 19 Nov 2024 21:00:02 -0800 Subject: [PATCH 031/118] add street-generated-random --- src/components/street-generated-random.js | 118 ++++++++++++++++++++++ src/index.js | 1 + 2 files changed, 119 insertions(+) create mode 100644 src/components/street-generated-random.js diff --git a/src/components/street-generated-random.js b/src/components/street-generated-random.js new file mode 100644 index 000000000..8b598bf56 --- /dev/null +++ b/src/components/street-generated-random.js @@ -0,0 +1,118 @@ +/* global AFRAME */ + +// a-frame component to generate cloned models along a street with random z position +// this moves logic from aframe-streetmix-parsers into this component +AFRAME.registerComponent('street-generated-random', { + multiple: true, + schema: { + model: { + type: 'string' + }, + length: { + // length in meters of linear path to fill with clones + type: 'number' + }, + count: { + // number of clones to create with random z + default: 1, + type: 'number' + }, + objLength: { + // length of the model in meters + default: 1, + type: 'number' + }, + positionX: { + // x position of clones along the length + default: 0, + type: 'number' + }, + positionY: { + // y position of clones along the length + default: 0, + type: 'number' + }, + facing: { + default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 + type: 'number' + }, + randomFacing: { + // if true, facing is ignored and a random Y Rotation is applied to each clone + default: false, + type: 'boolean' + } + // seed: { // seed not yet supported + // default: 0, + // type: 'number' + // } + }, + init: function () { + this.createdEntities = []; + }, + update: function (oldData) { + // generate a function that creates a cloned set of x entities based on spacing and length values from the model shortname gltf file loaded in aframe + const data = this.data; + // if oldData is same as current data, then don't update + if (AFRAME.utils.deepEqual(oldData, data)) { + return; + } + + // For each clone in this.entities, remove it + this.createdEntities.forEach((entity) => { + entity.remove(); + }); + this.createdEntities = []; + + // Calculate number of places needed based on length and objLength + const randPlaces = this.randPlacedElements( + data.length, + data.objLength, + data.count + ); + + // Create clones + randPlaces.forEach((randPosZ) => { + const clone = document.createElement('a-entity'); + clone.setAttribute('mixin', data.model); + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: randPosZ + }); + if (data.randomFacing) { + clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); + } else { + clone.setAttribute('rotation', `0 ${data.facing} 0`); + } + clone.classList.add('autocreated'); + // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings + clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); + + this.el.appendChild(clone); + this.createdEntities.push(clone); + }); + }, + randPlacedElements: function (streetLength, objLength, count) { + // Calculate placement length (spacing between objects) + const placeLength = objLength / 2 + objLength; + + // Calculate start and end positions + const start = -streetLength / 2 + placeLength / 2; + const end = streetLength / 2 - placeLength / 2; + + // Calculate number of possible positions + const len = Math.floor((end - start) / placeLength) + 1; + + // Generate array of evenly spaced positions + const positions = Array(len) + .fill() + .map((_, idx) => start + idx * placeLength); + + // Randomly shuffle positions + const shuffledPositions = positions.sort(() => 0.5 - Math.random()); + + // Return only requested number of positions + return shuffledPositions.slice(0, count); + } +}); diff --git a/src/index.js b/src/index.js index 09fc6b21b..311191f22 100644 --- a/src/index.js +++ b/src/index.js @@ -24,6 +24,7 @@ require('./components/obb-clipping.js'); require('./components/street-segment.js'); require('./components/street-generated-fixed.js'); require('./components/street-generated-single.js'); +require('./components/street-generated-random.js'); require('./editor/index.js'); const state = useStore.getState(); From b3daae858fd326ea4483398203302b27c036184f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 19 Nov 2024 21:00:17 -0800 Subject: [PATCH 032/118] use generated-random for bus --- src/aframe-streetmix-parsers.js | 28 +++++++--------------------- 1 file changed, 7 insertions(+), 21 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 6577192cd..dde653892 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -404,23 +404,6 @@ function createChooChooElement( return placedObjectEl; } -function createBusElement(variantList, length, showVehicles) { - if (!showVehicles) { - return; - } - const rotationY = variantList[0] === 'inbound' ? 0 : 180; - const busParentEl = document.createElement('a-entity'); - const busLength = 12; - const busObjectEl = document.createElement('a-entity'); - busObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); - busObjectEl.setAttribute('mixin', 'bus'); - const positionZ = randomPosition(busObjectEl, 'z', length, busLength); - busObjectEl.setAttribute('position', '0 0 ' + positionZ); - busParentEl.append(busObjectEl); - - return busParentEl; -} - function addLinearStreetAnimation( reusableObjectEl, speed, @@ -1170,10 +1153,13 @@ function processSegments( // get the color for a bus lane segmentColor = getSegmentColor(variantList[1]); - segmentParentEl.append( - createBusElement(variantList, length, showVehicles) - ); - + if (showVehicles) { + const rotationY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `model: bus; length: ${length}; objLength: 12; facing: ${rotationY}; count: 1;` + ); + } // create parent for the bus lane stencils to rotate the phrase instead of the word let reusableObjectStencilsParentEl; From 4ae767a61f10c2c6bcae4e1c6c5e09c63538a269 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 19 Nov 2024 22:02:34 -0800 Subject: [PATCH 033/118] move lrv and trolley --- src/aframe-streetmix-parsers.js | 43 +++++++---------------- src/components/street-generated-random.js | 11 +++--- 2 files changed, 16 insertions(+), 38 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index dde653892..009705220 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -384,26 +384,6 @@ function randomPosition(entity, axis, length, objSizeAttr = undefined) { return newPosition; } -function createChooChooElement( - variantList, - objectMixinId, - length, - showVehicles -) { - if (!showVehicles) { - return; - } - const rotationY = variantList[0] === 'inbound' ? 0 : 180; - const placedObjectEl = document.createElement('a-entity'); - const tramLength = 23; - placedObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); - placedObjectEl.setAttribute('mixin', objectMixinId); - placedObjectEl.setAttribute('class', objectMixinId); - const positionZ = randomPosition(placedObjectEl, 'z', length, tramLength); - placedObjectEl.setAttribute('position', '0 0 ' + positionZ); - return placedObjectEl; -} - function addLinearStreetAnimation( reusableObjectEl, speed, @@ -991,11 +971,15 @@ function processSegments( // get the color for a bus lane segmentColor = getSegmentColor(variantList[1]); // get the mixin id for the vehicle (is it a trolley or a tram?) - var objectMixinId = segments[i].type === 'streetcar' ? 'trolley' : 'tram'; - // create and append a train element - segmentParentEl.append( - createChooChooElement(variantList, objectMixinId, length, showVehicles) - ); + const objectMixinId = + segments[i].type === 'streetcar' ? 'trolley' : 'tram'; + if (showVehicles) { + const rotationY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `model: ${objectMixinId}; length: ${length}; placeLength: 23; facing: ${rotationY}; count: 1;` + ); + } // make the parent for all the objects to be cloned const tracksParentEl = createTracksParentElement(length, objectMixinId); // add these trains to the segment parent @@ -1157,7 +1141,7 @@ function processSegments( const rotationY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-random', - `model: bus; length: ${length}; objLength: 12; facing: ${rotationY}; count: 1;` + `model: bus; length: ${length}; placeLength: 15; facing: ${rotationY}; count: 1;` ); } // create parent for the bus lane stencils to rotate the phrase instead of the word @@ -1313,11 +1297,8 @@ function processSegments( `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotation}` ); } else if (segments[i].type === 'sidewalk-tree') { - if (variantList[0] === 'palm-tree') { - objectMixinId = 'palm-tree'; - } else { - objectMixinId = 'tree3'; - } + const objectMixinId = + variantList[0] === 'palm-tree' ? 'palm-tree' : 'tree3'; segmentParentEl.setAttribute( 'street-generated-fixed', `model: ${objectMixinId}; length: ${length}; randomFacing: true;` diff --git a/src/components/street-generated-random.js b/src/components/street-generated-random.js index 8b598bf56..a18e2269e 100644 --- a/src/components/street-generated-random.js +++ b/src/components/street-generated-random.js @@ -17,8 +17,8 @@ AFRAME.registerComponent('street-generated-random', { default: 1, type: 'number' }, - objLength: { - // length of the model in meters + placeLength: { + // length of the place for each model in meters default: 1, type: 'number' }, @@ -66,7 +66,7 @@ AFRAME.registerComponent('street-generated-random', { // Calculate number of places needed based on length and objLength const randPlaces = this.randPlacedElements( data.length, - data.objLength, + data.placeLength, data.count ); @@ -93,10 +93,7 @@ AFRAME.registerComponent('street-generated-random', { this.createdEntities.push(clone); }); }, - randPlacedElements: function (streetLength, objLength, count) { - // Calculate placement length (spacing between objects) - const placeLength = objLength / 2 + objLength; - + randPlacedElements: function (streetLength, placeLength, count) { // Calculate start and end positions const start = -streetLength / 2 + placeLength / 2; const end = streetLength / 2 - placeLength / 2; From 9f51879ffc44adccddb705730ccf6fe4c2373343 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 11:08:31 -0800 Subject: [PATCH 034/118] support brt station --- src/aframe-streetmix-parsers.js | 12 ++++-------- src/components/street-generated-single.js | 2 +- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 009705220..ac589cfc1 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -777,13 +777,6 @@ function createParkletElement(length, variantList) { return parkletParent; } -function createBrtStationElement() { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'brt-station'); - placedObjectEl.setAttribute('mixin', 'brt-station'); - return placedObjectEl; -} - // offset to center the street around global x position of 0 function createCenteredStreetElement(segments) { const streetEl = document.createElement('a-entity'); @@ -1353,7 +1346,10 @@ function processSegments( `model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` ); } else if (segments[i].type === 'brt-station') { - segmentParentEl.append(createBrtStationElement()); + segmentParentEl.setAttribute( + 'street-generated-single', + `model: brt-station; length: ${length};` + ); } else if ( segments[i].type === 'separator' && variantList[0] === 'dashed' diff --git a/src/components/street-generated-single.js b/src/components/street-generated-single.js index 9e970eacc..71cc186c0 100644 --- a/src/components/street-generated-single.js +++ b/src/components/street-generated-single.js @@ -82,7 +82,7 @@ AFRAME.registerComponent('street-generated-single', { } clone.classList.add('autocreated'); // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings - // clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-no-transform', ''); clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); this.el.appendChild(clone); this.createdEntities.push(clone); From 455ea8ce844bbb12e595bdd37398a8e7f1f5f151 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 11:12:50 -0800 Subject: [PATCH 035/118] outdoor dining --- src/aframe-streetmix-parsers.js | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index ac589cfc1..0025c15fc 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -630,23 +630,6 @@ function randPlacedElements(streetLength, objLength, count) { return allPlaces.slice(0, count); } -function createOutdoorDining(length, posY) { - const outdoorDiningParentEl = document.createElement('a-entity'); - const outdorDiningLength = 2.27; - - const randPlaces = randPlacedElements(length, outdorDiningLength, 5); - randPlaces.forEach((randPosZ) => { - const reusableObjectEl = document.createElement('a-entity'); - reusableObjectEl.setAttribute('mixin', 'outdoor_dining'); - - // const positionZ = randomPosition(reusableObjectEl, 'z', length, outdorDiningLength); - reusableObjectEl.setAttribute('position', { y: posY, z: randPosZ }); - outdoorDiningParentEl.append(reusableObjectEl); - }); - - return outdoorDiningParentEl; -} - function createMicroMobilityElement( variantList, segmentType, @@ -1273,7 +1256,10 @@ function processSegments( segmentParentEl.append(createMagicCarpetElement(showVehicles)); } else if (segments[i].type === 'outdoor-dining') { segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; - segmentParentEl.append(createOutdoorDining(length, 0)); + segmentParentEl.setAttribute( + 'street-generated-random', + `model: outdoor_dining; length: ${length}; placeLength: 2.27; count: 5;` + ); } else if (segments[i].type === 'parklet') { segmentPreset = 'drive-lane'; segmentParentEl.append(createParkletElement(length, variantList)); From a35a2093190a59aefe29e2bc3714ac1b3d80071a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 11:17:49 -0800 Subject: [PATCH 036/118] flex zone --- src/aframe-streetmix-parsers.js | 38 ++++++++------------------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 0025c15fc..21410aeff 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -688,30 +688,6 @@ function createMicroMobilityElement( return microMobilityParentEl; } -function createFlexZoneElement(variantList, length, showVehicles = true) { - if (!showVehicles) { - return; - } - const flexZoneParentEl = document.createElement('a-entity'); - const carLength = 5; - const carCount = getRandomIntInclusive(2, 4); - const randPlaces = randPlacedElements(length, carLength, carCount); - randPlaces.forEach((randPosZ) => { - const reusableObjectEl = document.createElement('a-entity'); - const rotationY = variantList[1] === 'inbound' ? 0 : 180; - reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); - if (variantList[0] === 'taxi') { - reusableObjectEl.setAttribute('mixin', 'sedan-taxi-rig'); - } else if (variantList[0] === 'rideshare') { - reusableObjectEl.setAttribute('mixin', 'sedan-rig'); - } - reusableObjectEl.setAttribute('position', { z: randPosZ }); - flexZoneParentEl.append(reusableObjectEl); - }); - - return flexZoneParentEl; -} - function createWayfindingElements() { const wayfindingParentEl = document.createElement('a-entity'); let reusableObjectEl; @@ -1183,12 +1159,16 @@ function processSegments( segmentParentEl.append(createFoodTruckElement(variantList, length)); } else if (segments[i].type === 'flex-zone') { segmentPreset = 'parking-lane'; - segmentParentEl.append( - createFlexZoneElement(variantList, length, showVehicles) - ); - + if (showVehicles) { + const objectMixinId = + variantList[0] === 'taxi' ? 'sedan-taxi-rig' : 'sedan-rig'; + const rotationY = variantList[1] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `model: ${objectMixinId}; length: ${length}; placeLength: 5; facing: ${rotationY}; count: 4;` + ); + } let reusableObjectStencilsParentEl; - reusableObjectStencilsParentEl = createStencilsParentElement({ y: 0.015, z: 5 From 8e984156e27abe5542b22bed5ed90d8ee8237b31 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 11:28:42 -0800 Subject: [PATCH 037/118] parklet --- src/aframe-streetmix-parsers.js | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 21410aeff..ef7904a64 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -719,23 +719,6 @@ function createWayfindingElements() { return wayfindingParentEl; } -function createParkletElement(length, variantList) { - const parkletParent = document.createElement('a-entity'); - const parkletLength = 4.03; - const parkletCount = 3; - const randPlaces = randPlacedElements(length, parkletLength, parkletCount); - randPlaces.forEach((randPosZ) => { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'parklet'); - placedObjectEl.setAttribute('position', { x: 0, y: 0.02, z: randPosZ }); - placedObjectEl.setAttribute('mixin', 'parklet'); - const rotationY = variantList[0] === 'left' ? 90 : 270; - placedObjectEl.setAttribute('rotation', { y: rotationY }); - parkletParent.append(placedObjectEl); - }); - return parkletParent; -} - // offset to center the street around global x position of 0 function createCenteredStreetElement(segments) { const streetEl = document.createElement('a-entity'); @@ -1242,7 +1225,11 @@ function processSegments( ); } else if (segments[i].type === 'parklet') { segmentPreset = 'drive-lane'; - segmentParentEl.append(createParkletElement(length, variantList)); + const rotationCloneY = variantList[0] === 'left' ? 90 : 270; + segmentParentEl.setAttribute( + 'street-generated-random', + `model: parklet; length: ${length}; placeLength: 4; count: 3; facing: ${rotationCloneY};` + ); } else if (segments[i].type === 'bikeshare') { const rotationCloneY = variantList[0] === 'left' ? 90 : 270; segmentParentEl.setAttribute( @@ -1250,10 +1237,10 @@ function processSegments( `model: bikeshare; length: ${length}; facing: ${rotationCloneY}; justify: middle;` ); } else if (segments[i].type === 'utilities') { - var rotation = variantList[0] === 'right' ? '180' : '0'; + const rotationCloneY = variantList[0] === 'right' ? 180 : 0; segmentParentEl.setAttribute( 'street-generated-fixed', - `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotation}` + `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotationCloneY}` ); } else if (segments[i].type === 'sidewalk-tree') { const objectMixinId = From d0c09a17c5a57c775e841edfbbd56f63e3723308 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 11:36:34 -0800 Subject: [PATCH 038/118] food truck / trailer --- src/aframe-streetmix-parsers.js | 31 +++++++------------------------ 1 file changed, 7 insertions(+), 24 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index ef7904a64..8a343c4ad 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -579,27 +579,6 @@ function createDriveLaneElement( return driveLaneParentEl; } -function createFoodTruckElement(variantList, length) { - const foodTruckParentEl = document.createElement('a-entity'); - - const reusableObjectEl = document.createElement('a-entity'); - const foodTruckLength = 7; - const rotationY = variantList[0] === 'left' ? 0 : 180; - reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); - reusableObjectEl.setAttribute('mixin', 'food-trailer-rig'); - - const positionZ = randomPosition( - reusableObjectEl, - 'z', - length, - foodTruckLength - ); - reusableObjectEl.setAttribute('positon', '0 0 ' + positionZ); - foodTruckParentEl.append(reusableObjectEl); - - return foodTruckParentEl; -} - function createMagicCarpetElement(showVehicles) { if (!showVehicles) { return; @@ -1139,16 +1118,20 @@ function processSegments( ); } else if (segments[i].type === 'food-truck') { segmentPreset = 'drive-lane'; - segmentParentEl.append(createFoodTruckElement(variantList, length)); + const rotationCloneY = variantList[0] === 'left' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `model: food-trailer-rig; length: ${length}; placeLength: 7; facing: ${rotationCloneY}; count: 2;` + ); } else if (segments[i].type === 'flex-zone') { segmentPreset = 'parking-lane'; if (showVehicles) { const objectMixinId = variantList[0] === 'taxi' ? 'sedan-taxi-rig' : 'sedan-rig'; - const rotationY = variantList[1] === 'inbound' ? 0 : 180; + const rotationCloneY = variantList[1] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-random', - `model: ${objectMixinId}; length: ${length}; placeLength: 5; facing: ${rotationY}; count: 4;` + `model: ${objectMixinId}; length: ${length}; placeLength: 5; facing: ${rotationCloneY}; count: 4;` ); } let reusableObjectStencilsParentEl; From 550eb8f28fce021711db2581833cf20a6e2dc3d6 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 13:24:59 -0800 Subject: [PATCH 039/118] randomly place an array of models --- src/components/street-generated-random.js | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/components/street-generated-random.js b/src/components/street-generated-random.js index a18e2269e..8792ec770 100644 --- a/src/components/street-generated-random.js +++ b/src/components/street-generated-random.js @@ -8,6 +8,9 @@ AFRAME.registerComponent('street-generated-random', { model: { type: 'string' }, + modelsArray: { + type: 'array' + }, length: { // length in meters of linear path to fill with clones type: 'number' @@ -73,7 +76,7 @@ AFRAME.registerComponent('street-generated-random', { // Create clones randPlaces.forEach((randPosZ) => { const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', data.model); + clone.setAttribute('mixin', this.getRandomMixin()); clone.setAttribute('position', { x: data.positionX, y: data.positionY, @@ -93,6 +96,15 @@ AFRAME.registerComponent('street-generated-random', { this.createdEntities.push(clone); }); }, + getRandomMixin: function () { + const data = this.data; + if (data.modelsArray && data.modelsArray.length > 0) { + return data.modelsArray[ + Math.floor(Math.random() * data.modelsArray.length) + ]; + } + return data.model; + }, randPlacedElements: function (streetLength, placeLength, count) { // Calculate start and end positions const start = -streetLength / 2 + placeLength / 2; From edda718af79fc2ec623e1fdb10caff681d90a7a6 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 13:25:37 -0800 Subject: [PATCH 040/118] random vehicles in drive-lane and turn-lane --- src/aframe-streetmix-parsers.js | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 8a343c4ad..f7b295721 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -888,7 +888,6 @@ function processSegments( const objectMixinId = segments[i].type === 'streetcar' ? 'trolley' : 'tram'; if (showVehicles) { - const rotationY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-random', `model: ${objectMixinId}; length: ${length}; placeLength: 23; facing: ${rotationY}; count: 1;` @@ -900,8 +899,16 @@ function processSegments( segmentParentEl.append(tracksParentEl); } else if (segments[i].type === 'turn-lane') { segmentPreset = 'drive-lane'; // use normal drive lane road material + const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + length: ${length}; + placeLength: 7.3; + facing: ${rotationCloneY}; + count: ${getRandomIntInclusive(2, 4)};` + ); var markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name - // Fix streetmix inbound turn lane orientation (change left to right) per: https://github.com/streetmix/streetmix/issues/683 if (variantList[0] === 'inbound') { markerMixinId = markerMixinId.replace(/left|right/g, function (m) { @@ -1102,19 +1109,15 @@ function processSegments( // add this stencil stuff to the segment parent segmentParentEl.append(reusableObjectStencilsParentEl); } else if (segments[i].type === 'drive-lane') { - const isAnimated = variantList[2] === 'animated' || globalAnimated; - const count = getRandomIntInclusive(2, 3); - const carStep = 7.3; - segmentParentEl.append( - createDriveLaneElement( - variantList, - segmentWidthInMeters, - length, - isAnimated, - showVehicles, - count, - carStep - ) + // const isAnimated = variantList[2] === 'animated' || globalAnimated; + const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + length: ${length}; + placeLength: 7.3; + facing: ${rotationCloneY}; + count: ${getRandomIntInclusive(2, 4)};` ); } else if (segments[i].type === 'food-truck') { segmentPreset = 'drive-lane'; From 99e0b3778c4d3fd8de4b2aefcee8f4dd4ff9fe1a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 13:36:51 -0800 Subject: [PATCH 041/118] support showVehicles conditional --- src/aframe-streetmix-parsers.js | 42 ++++++++++++++++++--------------- 1 file changed, 23 insertions(+), 19 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index f7b295721..5719b2b2a 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -899,15 +899,17 @@ function processSegments( segmentParentEl.append(tracksParentEl); } else if (segments[i].type === 'turn-lane') { segmentPreset = 'drive-lane'; // use normal drive lane road material - const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; - segmentParentEl.setAttribute( - 'street-generated-random', - `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; - length: ${length}; - placeLength: 7.3; - facing: ${rotationCloneY}; - count: ${getRandomIntInclusive(2, 4)};` - ); + if (showVehicles) { + const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + length: ${length}; + placeLength: 7.3; + facing: ${rotationCloneY}; + count: ${getRandomIntInclusive(2, 4)};` + ); + } var markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name // Fix streetmix inbound turn lane orientation (change left to right) per: https://github.com/streetmix/streetmix/issues/683 if (variantList[0] === 'inbound') { @@ -1109,16 +1111,18 @@ function processSegments( // add this stencil stuff to the segment parent segmentParentEl.append(reusableObjectStencilsParentEl); } else if (segments[i].type === 'drive-lane') { - // const isAnimated = variantList[2] === 'animated' || globalAnimated; - const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; - segmentParentEl.setAttribute( - 'street-generated-random', - `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; - length: ${length}; - placeLength: 7.3; - facing: ${rotationCloneY}; - count: ${getRandomIntInclusive(2, 4)};` - ); + if (showVehicles) { + // const isAnimated = variantList[2] === 'animated' || globalAnimated; + const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + length: ${length}; + placeLength: 7.3; + facing: ${rotationCloneY}; + count: ${getRandomIntInclusive(2, 4)};` + ); + } } else if (segments[i].type === 'food-truck') { segmentPreset = 'drive-lane'; const rotationCloneY = variantList[0] === 'left' ? 0 : 180; From 2f8879b60c8232bcde754da1fe88a4d5c9f7cb24 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 13:59:53 -0800 Subject: [PATCH 042/118] bike and scooter lane objects --- src/aframe-streetmix-parsers.js | 85 ++++----------------------------- 1 file changed, 9 insertions(+), 76 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 5719b2b2a..399708160 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -599,74 +599,6 @@ function createMagicCarpetElement(showVehicles) { return magicCarpetParentEl; } -function randPlacedElements(streetLength, objLength, count) { - const placeLength = objLength / 2 + objLength; - const allPlaces = getZPositions( - -streetLength / 2 + placeLength / 2, - streetLength / 2 - placeLength / 2, - placeLength - ); - return allPlaces.slice(0, count); -} - -function createMicroMobilityElement( - variantList, - segmentType, - length, - showVehicles, - animated = false -) { - if (!showVehicles) { - return; - } - const microMobilityParentEl = document.createElement('a-entity'); - - const bikeLength = 2.03; - const bikeCount = getRandomIntInclusive(2, 5); - - const cyclistMixins = [ - 'cyclist-cargo', - 'cyclist1', - 'cyclist2', - 'cyclist3', - 'cyclist-dutch', - 'cyclist-kid' - ]; - - const countCyclist = cyclistMixins.length; - let mixinId = 'Bicycle_1'; - const randPlaces = randPlacedElements(length, bikeLength, bikeCount); - randPlaces.forEach((randPosZ) => { - const reusableObjectEl = document.createElement('a-entity'); - const rotationY = variantList[0] === 'inbound' ? 0 : 180; - reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); - reusableObjectEl.setAttribute('position', { z: randPosZ }); - - if (animated) { - reusableObjectEl.setAttribute('animation-mixer', ''); - const speed = 5; - addLinearStreetAnimation( - reusableObjectEl, - speed, - length, - 0, - randPosZ, - variantList[0] - ); - } - if (segmentType === 'bike-lane') { - mixinId = cyclistMixins[getRandomIntInclusive(0, countCyclist)]; - } else { - mixinId = 'ElectricScooter_1'; - } - - reusableObjectEl.setAttribute('mixin', mixinId); - microMobilityParentEl.append(reusableObjectEl); - }); - - return microMobilityParentEl; -} - function createWayfindingElements() { const wayfindingParentEl = document.createElement('a-entity'); let reusableObjectEl; @@ -852,6 +784,7 @@ function processSegments( segments[i].type === 'bike-lane' || segments[i].type === 'scooter' ) { + segmentPreset = 'bike-lane'; // use bike lane road material // make a parent entity for the stencils const stencilsParentEl = createStencilsParentElement({ y: 0.015 @@ -868,14 +801,14 @@ function processSegments( }); // add this stencil stuff to the segment parent segmentParentEl.append(stencilsParentEl); - segmentParentEl.append( - createMicroMobilityElement( - variantList, - segments[i].type, - length, - showVehicles, - globalAnimated - ) + const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `modelsArray: cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid${segments[i].type === 'scooter' ? 'ElectricScooter_1' : ''}; + length: ${length}; + placeLength: 2.03; + facing: ${rotationCloneY}; + count: ${getRandomIntInclusive(2, 5)};` ); } else if ( segments[i].type === 'light-rail' || From e632f5189eae7191f4f74482f103f5bc69802f6a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 14:10:50 -0800 Subject: [PATCH 043/118] magic carpet --- src/aframe-streetmix-parsers.js | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 399708160..7619baaf6 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -579,26 +579,6 @@ function createDriveLaneElement( return driveLaneParentEl; } -function createMagicCarpetElement(showVehicles) { - if (!showVehicles) { - return; - } - const magicCarpetParentEl = document.createElement('a-entity'); - - const reusableObjectEl1 = document.createElement('a-entity'); - reusableObjectEl1.setAttribute('position', '0 1.75 0'); - reusableObjectEl1.setAttribute('rotation', '0 0 0'); - reusableObjectEl1.setAttribute('mixin', 'magic-carpet'); - magicCarpetParentEl.append(reusableObjectEl1); - const reusableObjectEl2 = document.createElement('a-entity'); - reusableObjectEl2.setAttribute('position', '0 1.75 0'); - reusableObjectEl2.setAttribute('rotation', '0 0 0'); - reusableObjectEl2.setAttribute('mixin', 'Character_1_M'); - magicCarpetParentEl.append(reusableObjectEl2); - - return magicCarpetParentEl; -} - function createWayfindingElements() { const wayfindingParentEl = document.createElement('a-entity'); let reusableObjectEl; @@ -1139,7 +1119,18 @@ function processSegments( // add bike racks to the segment parent } else if (segments[i].type === 'magic-carpet') { segmentPreset = 'drive-lane'; - segmentParentEl.append(createMagicCarpetElement(showVehicles)); + segmentParentEl.setAttribute( + 'street-generated-single', + `model: magic-carpet; + length: ${length}; + positionY: 1.2;` + ); + segmentParentEl.setAttribute( + 'street-generated-single__2', + `model: Character_1_M; + length: ${length}; + positionY: 1.2;` + ); } else if (segments[i].type === 'outdoor-dining') { segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; segmentParentEl.setAttribute( @@ -1194,7 +1185,7 @@ function processSegments( (variantList[0] === 'right' || variantList[0] === 'both') ) { segmentParentEl.setAttribute( - 'street-generated-fixed__pride-flag', + 'street-generated-fixed__2', `model: pride-flag; length: ${length}; cycleOffset: 0.4; positionX: 0.409; positionY: 5;` ); } @@ -1203,7 +1194,7 @@ function processSegments( (variantList[0] === 'left' || variantList[0] === 'both') ) { segmentParentEl.setAttribute( - 'street-generated-fixed__pride-flag', + 'street-generated-fixed__2', `model: pride-flag; length: ${length}; facing: 180; cycleOffset: 0.4; positionX: -0.409; positionY: 5;` ); } From 1817f03a5cf616b9dae5f5bb1c4307d89bfff824 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 22 Nov 2024 15:14:36 -0800 Subject: [PATCH 044/118] support naive stencils --- src/aframe-streetmix-parsers.js | 18 ++++-------------- src/components/street-generated-fixed.js | 11 +++++++++-- 2 files changed, 13 insertions(+), 16 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 7619baaf6..e3c6a5a23 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -746,20 +746,10 @@ function processSegments( // look at segment type and variant(s) to determine specific cases if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { - // make a parent entity for the stencils - const stencilsParentEl = createStencilsParentElement({ - y: 0.015 - }); - // clone a bunch of stencil entities (note: this is not draw call efficient) - cloneMixinAsChildren({ - objectMixinId: 'stencils sharrow', - parentEl: stencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 10, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(stencilsParentEl); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: stencils sharrow; length: ${length}; rotationX: -90; positionY: 0.15; cycleOffset: 0.2; spacing: 15;` + ); } else if ( segments[i].type === 'bike-lane' || segments[i].type === 'scooter' diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js index 020fc9986..6c040141c 100644 --- a/src/components/street-generated-fixed.js +++ b/src/components/street-generated-fixed.js @@ -41,6 +41,10 @@ AFRAME.registerComponent('street-generated-fixed', { // if true, facing is ignored and a random Y Rotation is applied to each clone default: false, type: 'boolean' + }, + rotationX: { + default: 0, + type: 'number' } // seed: { // seed not yet supported // default: 0, @@ -84,9 +88,12 @@ AFRAME.registerComponent('street-generated-fixed', { }); if (data.randomFacing) { - clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); + clone.setAttribute( + 'rotation', + `${data.rotationX} ${Math.random() * 360} 0` + ); } else { - clone.setAttribute('rotation', `0 ${data.facing} 0`); + clone.setAttribute('rotation', `${data.rotationX} ${data.facing} 0`); } clone.classList.add('autocreated'); // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings From d96a7d7d4f8031e63da8d934b7a6b303f644841e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 24 Nov 2024 11:10:08 -0800 Subject: [PATCH 045/118] new stencil component also experimental data format export --- src/aframe-streetmix-parsers.js | 37 +++++++- src/components/street-generated-fixed.js | 11 +-- src/components/street-generated-stencil.js | 100 +++++++++++++++++++++ src/index.js | 1 + 4 files changed, 138 insertions(+), 11 deletions(-) create mode 100644 src/components/street-generated-stencil.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 56d84f177..bbfdee7ac 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -707,6 +707,9 @@ function processSegments( streetParentEl.setAttribute('data-layer-name', 'Street Segments Container'); streetParentEl.setAttribute('data-no-transform', ''); + // experimental - create a new array children for the new data structure + let newManagedStreetDataStructureChildren = []; + var cumulativeWidthInMeters = 0; for (var i = 0; i < segments.length; i++) { var segmentColor = null; @@ -747,8 +750,8 @@ function processSegments( // look at segment type and variant(s) to determine specific cases if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { segmentParentEl.setAttribute( - 'street-generated-fixed', - `model: stencils sharrow; length: ${length}; rotationX: -90; positionY: 0.15; cycleOffset: 0.2; spacing: 15;` + 'street-generated-stencil', + `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15;` ); } else if ( segments[i].type === 'bike-lane' || @@ -1354,6 +1357,14 @@ function processSegments( 'surface', TYPES[segmentPreset]?.surface ); + // experimental - output the new data structure + let childData = { + id: segments[i].id, // this will collide with other segment ID if there are multiple streets placed with identical segment id's + type: segments[i].type, + width: segmentWidthInMeters, + elevation: elevation + }; + newManagedStreetDataStructureChildren.push(childData); } else { segmentParentEl.append( createSeparatorElement( @@ -1375,6 +1386,28 @@ function processSegments( 'Segment • ' + segments[i].type + ', ' + variantList[0] ); } + // experimental, output the new data structure + let newManagedStreetDataStructureInstance = { + // name: "string", // streetmix name not accessible from this function + type: 'managed_street', + width: cumulativeWidthInMeters, // this is the user-specified RoW width, not cumulative width of segments + // length: float, // not accessible from this function + // transform: { + // position: { x: float, y: float, z: float} + // rotation: { x: float, y: float, z: float} + // scale: { x: float, y: float, z: float} + // }, + children: newManagedStreetDataStructureChildren + }; + console.log(newManagedStreetDataStructureInstance); + document + .querySelector('#canvas-plane') + .setAttribute( + 'drawCanvas', + 'myCanvas: my-canvas; managedStreet: ' + + JSON.stringify(newManagedStreetDataStructureInstance) + ); + // create new brown box to represent ground underneath street const dirtBox = document.createElement('a-box'); const xPos = cumulativeWidthInMeters / 2; diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js index 6c040141c..020fc9986 100644 --- a/src/components/street-generated-fixed.js +++ b/src/components/street-generated-fixed.js @@ -41,10 +41,6 @@ AFRAME.registerComponent('street-generated-fixed', { // if true, facing is ignored and a random Y Rotation is applied to each clone default: false, type: 'boolean' - }, - rotationX: { - default: 0, - type: 'number' } // seed: { // seed not yet supported // default: 0, @@ -88,12 +84,9 @@ AFRAME.registerComponent('street-generated-fixed', { }); if (data.randomFacing) { - clone.setAttribute( - 'rotation', - `${data.rotationX} ${Math.random() * 360} 0` - ); + clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); } else { - clone.setAttribute('rotation', `${data.rotationX} ${data.facing} 0`); + clone.setAttribute('rotation', `0 ${data.facing} 0`); } clone.classList.add('autocreated'); // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js new file mode 100644 index 000000000..49ea679e5 --- /dev/null +++ b/src/components/street-generated-stencil.js @@ -0,0 +1,100 @@ +/* global AFRAME */ + +// a-frame component to generate cloned models along a street +// this moves logic from aframe-streetmix-parsers into this component + +AFRAME.registerComponent('street-generated-stencil', { + multiple: true, + schema: { + model: { + type: 'string' + }, + length: { + // length in meters of linear path to fill with clones + type: 'number' + }, + spacing: { + // spacing in meters between clones + default: 15, + type: 'number' + }, + positionX: { + // x position of clones along the length + default: 0, + type: 'number' + }, + positionY: { + // y position of clones along the length + default: 0.15, + type: 'number' + }, + cycleOffset: { + // z (inbound/outbound) offset as a fraction of spacing value + default: 0.5, // this is used to place different models at different z-levels with the same spacing value + type: 'number' + }, + facing: { + default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 + type: 'number' + }, + randomFacing: { + // if true, facing is ignored and a random Y Rotation is applied to each clone + default: false, + type: 'boolean' + } + // seed: { // seed not yet supported + // default: 0, + // type: 'number' + // } + }, + init: function () { + this.createdEntities = []; + }, + update: function (oldData) { + // generate a function that creates a cloned set of x entities based on spacing and length values from the model shortname gltf file loaded in aframe + const data = this.data; + // if oldData is same as current data, then don't update + if (AFRAME.utils.deepEqual(oldData, data)) { + return; + } + + // For each clone in this.entities, remove it + this.createdEntities.forEach((entity) => { + entity.remove(); + }); + this.createdEntities = []; + + this.correctedSpacing = data.spacing < 1 ? 1 : data.spacing; // return 1 if data.spacing is less than 1 + + // Calculate number of clones needed based on length and spacing + const numClones = Math.floor(data.length / this.correctedSpacing); + + // Create clones and position them along the length + for (let i = 0; i < numClones; i++) { + const clone = document.createElement('a-entity'); + clone.setAttribute('mixin', data.model); + // Position each clone evenly spaced along z-axis + // offset default is 0.5 so that clones don't start exactly at street start which looks weird + const positionZ = + data.length / 2 - (i + data.cycleOffset) * this.correctedSpacing; + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: positionZ + }); + + if (data.randomFacing) { + clone.setAttribute('rotation', `-90 ${Math.random() * 360} 0`); + } else { + clone.setAttribute('rotation', `-90 ${data.facing} 0`); + } + clone.classList.add('autocreated'); + // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings + clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); + + this.el.appendChild(clone); + this.createdEntities.push(clone); + } + } +}); diff --git a/src/index.js b/src/index.js index 311191f22..78e85640d 100644 --- a/src/index.js +++ b/src/index.js @@ -25,6 +25,7 @@ require('./components/street-segment.js'); require('./components/street-generated-fixed.js'); require('./components/street-generated-single.js'); require('./components/street-generated-random.js'); +require('./components/street-generated-stencil.js'); require('./editor/index.js'); const state = useStore.getState(); From 14ed05299542b82974596fe0fea049cb1d9396b7 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 25 Nov 2024 13:21:57 -0800 Subject: [PATCH 046/118] committing wip street label to return to this soon --- src/components/street-generated-label.js | 112 +++++++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 src/components/street-generated-label.js diff --git a/src/components/street-generated-label.js b/src/components/street-generated-label.js new file mode 100644 index 000000000..797f6a998 --- /dev/null +++ b/src/components/street-generated-label.js @@ -0,0 +1,112 @@ +/* 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... + // } + // }); + // + } +}); From a9f5180f51f26e123326cca08dc984b4df95dd95 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 25 Nov 2024 16:54:15 -0800 Subject: [PATCH 047/118] remove canvas-plane error --- src/aframe-streetmix-parsers.js | 7 ------- 1 file changed, 7 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index bbfdee7ac..3d280acf7 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -1400,13 +1400,6 @@ function processSegments( children: newManagedStreetDataStructureChildren }; console.log(newManagedStreetDataStructureInstance); - document - .querySelector('#canvas-plane') - .setAttribute( - 'drawCanvas', - 'myCanvas: my-canvas; managedStreet: ' + - JSON.stringify(newManagedStreetDataStructureInstance) - ); // create new brown box to represent ground underneath street const dirtBox = document.createElement('a-box'); From 2270bf081fcc90c54ef5d9d3d7a9e761a7a198cd Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 25 Nov 2024 16:55:45 -0800 Subject: [PATCH 048/118] bike arrow stencil --- src/aframe-streetmix-parsers.js | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 3d280acf7..60b78010d 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -758,22 +758,12 @@ function processSegments( segments[i].type === 'scooter' ) { segmentPreset = 'bike-lane'; // use bike lane road material - // make a parent entity for the stencils - const stencilsParentEl = createStencilsParentElement({ - y: 0.015 - }); // get the mixin id for a bike lane segmentColor = getSegmentColor(variantList[1]); - // clone a bunch of stencil entities (note: this is not draw call efficient) - cloneMixinAsChildren({ - objectMixinId: 'stencils bike-arrow', - parentEl: stencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 20, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(stencilsParentEl); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20;` + ); const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-random', From b1681daf8b5ef836af6bdd188a2c9df84d356d60 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 25 Nov 2024 17:10:29 -0800 Subject: [PATCH 049/118] turn lanes including shared turn --- src/aframe-streetmix-parsers.js | 41 ++++++++------------------------- 1 file changed, 9 insertions(+), 32 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 60b78010d..9fde224e9 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -738,8 +738,6 @@ function processSegments( // If segment variant inbound, rotate segment model by 180 degrees var rotationY = variantList[0] === 'inbound' || variantList[1] === 'inbound' ? 180 : 0; - var isOutbound = - variantList[0] === 'outbound' || variantList[1] === 'outbound' ? 1 : -1; // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that var segmentPreset = segments[i].type; @@ -762,7 +760,7 @@ function processSegments( segmentColor = getSegmentColor(variantList[1]); segmentParentEl.setAttribute( 'street-generated-stencil', - `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20;` + `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20; facing: ${rotationY};` ); const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( @@ -819,36 +817,15 @@ function processSegments( if (variantList[1] === 'left-right-straight') { markerMixinId = 'all'; } - var mixinString = 'stencils ' + markerMixinId; - - // make the parent for all the objects to be cloned - const stencilsParentEl = createStencilsParentElement({ - y: 0.015 - }); - cloneMixinAsChildren({ - objectMixinId: mixinString, - parentEl: stencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 15, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(stencilsParentEl); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.4; spacing: 20; facing: ${rotationY};` + ); if (variantList[1] === 'shared') { - // add an additional marking to represent the opposite turn marking stencil (rotated 180º) - const stencilsParentEl = createStencilsParentElement({ - y: 0.015, - z: -3 * isOutbound - }); - cloneMixinAsChildren({ - objectMixinId: mixinString, - parentEl: stencilsParentEl, - rotation: '-90 ' + (rotationY + 180) + ' 0', - step: 15, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(stencilsParentEl); + segmentParentEl.setAttribute( + 'street-generated-stencil__2', + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.3; spacing: 20; facing: ${rotationY + 180};` + ); } } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { segmentPreset = 'divider'; From 579b7504299491b5d1516596c683a8c5db7906d1 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 26 Nov 2024 14:32:51 -0800 Subject: [PATCH 050/118] barely working parking variants --- src/aframe-streetmix-parsers.js | 248 ++------------------- src/components/street-generated-stencil.js | 11 + 2 files changed, 27 insertions(+), 232 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 9fde224e9..8aaf26bc3 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -1,5 +1,3 @@ -/* global THREE */ - // Orientation - default model orientation is "outbound" (away from camera) var streetmixParsersTested = require('./tested/aframe-streetmix-parsers-tested'); var { segmentVariants } = require('./segments-variants.js'); @@ -354,36 +352,6 @@ function getSegmentColor(variant) { return COLORS.white; } -function getDimensions(object3d) { - var box = new THREE.Box3().setFromObject(object3d); - var x = box.max.x - box.min.x; - var y = box.max.y - box.min.y; - var z = box.max.z - box.min.z; - - return { x, y, z }; -} - -function getStartEndPosition(streetLength, objectLength) { - // get the start and end position for placing an object on a line - // computed by length of the street and object's length - const start = -0.5 * streetLength + 0.5 * objectLength; - const end = 0.5 * streetLength - 0.5 * objectLength; - return { start, end }; -} - -function randomPosition(entity, axis, length, objSizeAttr = undefined) { - // place randomly an element on a line length='length' on the axis 'axis' - // Need to call from 'model-loaded' event if objSizeAttr is undefined - // existEnts - array with existing entities (for prevent intersection) - const newObject = entity.object3D; - const objSize = objSizeAttr || getDimensions(newObject)[axis]; - const { start, end } = getStartEndPosition(length, objSize); - const setFunc = `set${axis.toUpperCase()}`; - const newPosition = getRandomArbitrary(start, end); - newObject.position[setFunc](newPosition); - return newPosition; -} - function addLinearStreetAnimation( reusableObjectEl, speed, @@ -421,164 +389,6 @@ function addLinearStreetAnimation( return reusableObjectEl; } -function createDriveLaneElement( - variantList, - segmentWidthInMeters, - streetLength, - animated = false, - showVehicles = true, - count = 1, - carStep = undefined -) { - if (!showVehicles) { - return; - } - let speed = 0; - let [lineVariant, direction, carType] = variantList; - if (variantList.length === 2) { - carType = direction; - direction = lineVariant; - } - - const rotationVariants = { - inbound: 0, - outbound: 180, - sideways: { - left: -90, - right: 90 - }, - 'angled-front-left': -60, - 'angled-front-right': 60, - 'angled-rear-left': -120, - 'angled-rear-right': 120 - }; - let rotationY; - if (lineVariant === 'sideways') { - rotationY = rotationVariants['sideways'][direction]; - } else { - rotationY = rotationVariants[lineVariant]; - } - - if (carType === 'pedestrian') { - return createSidewalkClonedVariants( - segmentWidthInMeters, - 'normal', - streetLength, - direction, - animated - ); - } - - const driveLaneParentEl = document.createElement('a-entity'); - - if (variantList.length === 1) { - // if there is no cars - return driveLaneParentEl; - } - - const carParams = { - car: { - mixin: 'sedan-rig', - wheelDiameter: 0.76, - length: 5.17, - width: 2 - }, - microvan: { - mixin: 'suv-rig', - wheelDiameter: 0.84, - length: 5, - width: 2 - }, - truck: { - mixin: 'box-truck-rig', - wheelDiameter: 1.05, - length: 6.95, - width: 2.5 - }, - // autonomous vehicle - av: { - mixin: 'self-driving-cruise-car-rig', - wheelDiameter: 0.76, - length: 5.17, - width: 2 - } - }; - - // default drive-lane variant if selected variant (carType) is not supported - if (!carParams[carType]) { - carType = 'car'; - } - function createCar(positionZ = undefined, carType = 'car') { - const params = carParams[carType]; - - const reusableObjectEl = document.createElement('a-entity'); - - if (!positionZ) { - positionZ = randomPosition( - reusableObjectEl, - 'z', - streetLength, - params['length'] - ); - } - reusableObjectEl.setAttribute('position', `0 0 ${positionZ}`); - reusableObjectEl.setAttribute('mixin', params['mixin']); - reusableObjectEl.setAttribute('rotation', `0 ${rotationY} 0`); - - if (animated) { - speed = 5; // meters per second - reusableObjectEl.setAttribute('wheel', { - speed: speed, - wheelDiameter: params['wheelDiameter'] - }); - addLinearStreetAnimation( - reusableObjectEl, - speed, - streetLength, - 0, - positionZ, - direction - ); - } - driveLaneParentEl.append(reusableObjectEl); - return reusableObjectEl; - } - - // create one or more randomly placed cars - - if (count > 1) { - const halfStreet = streetLength / 2; - const halfParkingLength = carStep / 2 + carStep; - const allPlaces = getZPositions( - -halfStreet + halfParkingLength, - halfStreet - halfParkingLength, - carStep - ); - const randPlaces = allPlaces.slice(0, count); - const carSizeZ = - lineVariant === 'sideways' || lineVariant.includes('angled') - ? 'width' - : 'length'; - - const carSizeValueZ = carParams[carType][carSizeZ]; - - randPlaces.forEach((randPositionZ) => { - const maxDist = carStep - carSizeValueZ - 0.2; - // randOffset is for randomly displacement in a parking space (+/- maxDist) - const randOffset = -maxDist / 2 + maxDist * Math.random(); - if (maxDist > 0) { - // if the car fits in the parking space - const positionZ = randPositionZ + randOffset; - createCar(positionZ, carType); - } - }); - } else { - createCar(undefined, carType); - } - - return driveLaneParentEl; -} - function createWayfindingElements() { const wayfindingParentEl = document.createElement('a-entity'); let reusableObjectEl; @@ -1220,15 +1030,12 @@ function processSegments( repeatCount[0] = 1; repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'parking-lane') { - let reusableObjectStencilsParentEl; - segmentPreset = 'parking-lane'; let parkingMixin = 'stencils parking-t'; - - const carCount = 5; let carStep = 6; const rotationVars = { + // markings rotation outbound: 90, inbound: 90, sideways: 0, @@ -1255,49 +1062,26 @@ function processSegments( markingPosX = 0; parkingMixin = 'solid-stripe'; } - const markingPosXY = markingPosX + ' 0'; - const clonedStencilRadius = length / 2 - carStep; - segmentParentEl.append( - createDriveLaneElement( - [...variantList, 'car'], - segmentWidthInMeters, - length, - false, - showVehicles, - carCount, - carStep - ) + segmentParentEl.setAttribute( + 'street-generated-random', + `modelsArray: sedan-rig, self-driving-waymo-car, suv-rig; + length: ${length}; + placeLength: ${carStep}; + count: ${getRandomIntInclusive(6, 8)}; + facing: ${markingsRotZ - 90};` // this needs work -- the rotation is off by 180 degrees on the right side for perpendicular and angled variants ); if (variantList[1] === 'left') { - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015 - }); - cloneMixinAsChildren({ - objectMixinId: parkingMixin, - parentEl: reusableObjectStencilsParentEl, - positionXYString: markingPosXY, - rotation: '-90 ' + '90 ' + markingsRotZ, - length: markingLength, - step: carStep, - radius: clonedStencilRadius - }); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: ${parkingMixin}; length: ${length}; cycleOffset: 1; spacing: ${carStep}; positionX: ${markingPosX}; facing: ${markingsRotZ + 90}; stencilHeight: ${markingLength};` + ); } else { - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015 - }); - cloneMixinAsChildren({ - objectMixinId: parkingMixin, - parentEl: reusableObjectStencilsParentEl, - positionXYString: markingPosXY, - rotation: '-90 ' + '90 ' + markingsRotZ, - length: markingLength, - step: carStep, - radius: clonedStencilRadius - }); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: ${parkingMixin}; length: ${length}; cycleOffset: 1; spacing: ${carStep}; positionX: ${markingPosX}; facing: ${markingsRotZ + 90}; stencilHeight: ${markingLength};` + ); } - // add the stencils to the segment parent - segmentParentEl.append(reusableObjectStencilsParentEl); } // if this thing is a sidewalk, make segmentPreset sidewalk diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 49ea679e5..126d44691 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -41,6 +41,10 @@ AFRAME.registerComponent('street-generated-stencil', { // if true, facing is ignored and a random Y Rotation is applied to each clone default: false, type: 'boolean' + }, + stencilHeight: { + default: 0, + type: 'number' } // seed: { // seed not yet supported // default: 0, @@ -83,6 +87,13 @@ AFRAME.registerComponent('street-generated-stencil', { z: positionZ }); + if (data.stencilHeight > 0) { + clone.addEventListener('loaded', (evt) => { + evt.target.setAttribute('geometry', 'height', data.stencilHeight); + evt.target.setAttribute('atlas-uvs', 'forceRefresh', true); // this shouldn't be necessary, let's get rid of atlas uv + }); + } + if (data.randomFacing) { clone.setAttribute('rotation', `-90 ${Math.random() * 360} 0`); } else { From 6a6a580b82a6489f902d03d2230b7b67d82243a9 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 26 Nov 2024 14:38:19 -0800 Subject: [PATCH 051/118] no vehicles in shared center turn lane --- src/aframe-streetmix-parsers.js | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 8aaf26bc3..6db627443 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -201,10 +201,6 @@ function insertSeparatorSegments(segments) { }, [] ); - - // console.log('newValues =', newValues) - // console.log(segments); - return newValues; } @@ -603,7 +599,7 @@ function processSegments( segmentParentEl.append(tracksParentEl); } else if (segments[i].type === 'turn-lane') { segmentPreset = 'drive-lane'; // use normal drive lane road material - if (showVehicles) { + if (showVehicles && variantList[1] !== 'shared') { const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-random', From d1a537dd4bd93b09a36cda0f3ef427f675bfe486 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 26 Nov 2024 15:07:06 -0800 Subject: [PATCH 052/118] lower stencil y --- src/components/street-generated-stencil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 126d44691..e1f62c66c 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -25,7 +25,7 @@ AFRAME.registerComponent('street-generated-stencil', { }, positionY: { // y position of clones along the length - default: 0.15, + default: 0.05, type: 'number' }, cycleOffset: { From d2febe62ca3c8aa70cec172896106ed25efaea1a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 26 Nov 2024 20:37:31 -0800 Subject: [PATCH 053/118] support multiple stencils --- src/aframe-streetmix-parsers.js | 132 ++------------------- src/components/street-generated-stencil.js | 105 +++++++++------- 2 files changed, 72 insertions(+), 165 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 6db627443..26d193947 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -47,47 +47,6 @@ const TYPES = { } }; -function cloneMixinAsChildren({ - objectMixinId = '', - parentEl = null, - step = 15, - radius = 60, - rotation = '0 0 0', - positionXYString = '0 0', - length = undefined, - randomY = false -}) { - for (let j = radius * -1; j <= radius; j = j + step) { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('mixin', objectMixinId); - placedObjectEl.setAttribute('class', objectMixinId); - placedObjectEl.setAttribute('position', positionXYString + ' ' + j); - - if (length) { - placedObjectEl.addEventListener('loaded', (evt) => { - evt.target.setAttribute('geometry', 'height', length); - evt.target.setAttribute('atlas-uvs', 'c', 1); - }); - } - - if (randomY) { - placedObjectEl.setAttribute( - 'rotation', - '0 ' + Math.floor(randomTestable() * 361) + ' 0' - ); - } else { - placedObjectEl.setAttribute('rotation', rotation); - } - // add the new elmement to DOM - parentEl.append(placedObjectEl); - // could be good to use geometry merger https://github.com/supermedium/superframe/tree/master/components/geometry-merger - } -} - -function randomTestable() { - return Math.random(); -} - // this function takes a list of segments and adds lane markings or "separator segments" // these are 0 width segments inserted into the street json prior to rendering // the basic logic is: if there are two adjacent "lane-ish" segments, then add lane separators @@ -204,13 +163,6 @@ function insertSeparatorSegments(segments) { return newValues; } -function createStencilsParentElement(position) { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('class', 'stencils-parent'); - placedObjectEl.setAttribute('position', position); // position="1.043 0.100 -3.463" - return placedObjectEl; -} - function createRailsElement(length, railsPosX) { const placedObjectEl = document.createElement('a-entity'); const railsGeometry = { @@ -497,11 +449,6 @@ function processSegments( globalAnimated, showVehicles ) { - var clonedObjectRadius = length / 2; - // Adjust clonedObjectRadius so that objects do not repeat - if (length > 12) { - clonedObjectRadius = (length - 12) / 2; - } // add additional 0-width segments for stripes (painted markers) if (showStriping) { segments = insertSeparatorSegments(segments); @@ -746,49 +693,10 @@ function processSegments( `model: bus; length: ${length}; placeLength: 15; facing: ${rotationY}; count: 1;` ); } - // create parent for the bus lane stencils to rotate the phrase instead of the word - let reusableObjectStencilsParentEl; - - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015 - }); - cloneMixinAsChildren({ - objectMixinId: 'stencils word-bus', - parentEl: reusableObjectStencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 50, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(reusableObjectStencilsParentEl); - - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015, - z: 10 - }); - cloneMixinAsChildren({ - objectMixinId: 'stencils word-taxi', - parentEl: reusableObjectStencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 50, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(reusableObjectStencilsParentEl); - - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015, - z: 20 - }); - cloneMixinAsChildren({ - objectMixinId: 'stencils word-only', - parentEl: reusableObjectStencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 50, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(reusableObjectStencilsParentEl); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `stencils: word-only, word-taxi, word-bus; length: ${length}; spacing: 40; padding: 10; facing: ${rotationY}` + ); } else if (segments[i].type === 'drive-lane') { if (showVehicles) { // const isAnimated = variantList[2] === 'animated' || globalAnimated; @@ -820,34 +728,10 @@ function processSegments( `model: ${objectMixinId}; length: ${length}; placeLength: 5; facing: ${rotationCloneY}; count: 4;` ); } - let reusableObjectStencilsParentEl; - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015, - z: 5 - }); - cloneMixinAsChildren({ - objectMixinId: 'stencils word-loading-small', - parentEl: reusableObjectStencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 50, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(reusableObjectStencilsParentEl); - - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: 0.015, - z: -5 - }); - cloneMixinAsChildren({ - objectMixinId: 'stencils word-only-small', - parentEl: reusableObjectStencilsParentEl, - rotation: '-90 ' + rotationY + ' 0', - step: 50, - radius: clonedObjectRadius - }); - // add this stencil stuff to the segment parent - segmentParentEl.append(reusableObjectStencilsParentEl); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `stencils: word-loading-small, word-only-small; length: ${length}; spacing: 40; padding: 10; facing: ${rotationY}` + ); } else if (segments[i].type === 'sidewalk' && variantList[0] !== 'empty') { // handles variantString with value sparse, normal, or dense sidewalk const isAnimated = variantList[1] === 'animated' || globalAnimated; diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index e1f62c66c..0a837d272 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -9,6 +9,15 @@ AFRAME.registerComponent('street-generated-stencil', { model: { type: 'string' }, + stencils: { + // if present, then use this array of stencils instead of 1 model + type: 'array' + }, + padding: { + // distance between stencils within array + default: 0, + type: 'number' + }, length: { // length in meters of linear path to fill with clones type: 'number' @@ -55,57 +64,71 @@ AFRAME.registerComponent('street-generated-stencil', { this.createdEntities = []; }, update: function (oldData) { - // generate a function that creates a cloned set of x entities based on spacing and length values from the model shortname gltf file loaded in aframe const data = this.data; - // if oldData is same as current data, then don't update - if (AFRAME.utils.deepEqual(oldData, data)) { - return; - } + if (AFRAME.utils.deepEqual(oldData, data)) return; - // For each clone in this.entities, remove it - this.createdEntities.forEach((entity) => { - entity.remove(); - }); + // Clean up old entities + this.createdEntities.forEach((entity) => entity.remove()); this.createdEntities = []; - this.correctedSpacing = data.spacing < 1 ? 1 : data.spacing; // return 1 if data.spacing is less than 1 + // Use either stencils array or single model + let stencilsToUse = data.stencils.length > 0 ? data.stencils : [data.model]; - // Calculate number of clones needed based on length and spacing - const numClones = Math.floor(data.length / this.correctedSpacing); + // Reverse stencil order if facing is 180 degrees + if (data.facing === 180) { + stencilsToUse = stencilsToUse.slice().reverse(); + } - // Create clones and position them along the length - for (let i = 0; i < numClones; i++) { - const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', data.model); - // Position each clone evenly spaced along z-axis - // offset default is 0.5 so that clones don't start exactly at street start which looks weird - const positionZ = - data.length / 2 - (i + data.cycleOffset) * this.correctedSpacing; - clone.setAttribute('position', { - x: data.positionX, - y: data.positionY, - z: positionZ - }); + // Ensure minimum spacing + this.correctedSpacing = Math.max(1, data.spacing); - if (data.stencilHeight > 0) { - clone.addEventListener('loaded', (evt) => { - evt.target.setAttribute('geometry', 'height', data.stencilHeight); - evt.target.setAttribute('atlas-uvs', 'forceRefresh', true); // this shouldn't be necessary, let's get rid of atlas uv + // Calculate number of stencil groups that can fit in the length + const numGroups = Math.floor(data.length / this.correctedSpacing); + + // Create stencil groups along the street + for (let groupIndex = 0; groupIndex < numGroups; groupIndex++) { + const groupPosition = + data.length / 2 - + (groupIndex + data.cycleOffset) * this.correctedSpacing; + + // Create each stencil within the group + stencilsToUse.forEach((stencilName, stencilIndex) => { + const clone = document.createElement('a-entity'); + clone.setAttribute('mixin', stencilName); + + // Calculate stencil position within group + const stencilOffset = + (stencilIndex - (stencilsToUse.length - 1) / 2) * data.padding; + + // Set position with group position and stencil offset + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: groupPosition + stencilOffset }); - } - if (data.randomFacing) { - clone.setAttribute('rotation', `-90 ${Math.random() * 360} 0`); - } else { - clone.setAttribute('rotation', `-90 ${data.facing} 0`); - } - clone.classList.add('autocreated'); - // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings - clone.setAttribute('data-no-transform', ''); - clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); + // Handle stencil height if specified + if (data.stencilHeight > 0) { + clone.addEventListener('loaded', (evt) => { + evt.target.setAttribute('geometry', 'height', data.stencilHeight); + evt.target.setAttribute('atlas-uvs', 'forceRefresh', true); + }); + } - this.el.appendChild(clone); - this.createdEntities.push(clone); + // Set rotation - either random or specified facing + const rotation = data.randomFacing + ? `-90 ${Math.random() * 360} 0` + : `-90 ${data.facing} 0`; + clone.setAttribute('rotation', rotation); + + // Add metadata + clone.classList.add('autocreated'); + clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', `Cloned Model • ${stencilName}`); + + this.el.appendChild(clone); + this.createdEntities.push(clone); + }); } } }); From 591e3d8d4b25622e7cd3d784767e1237dd151a51 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 26 Nov 2024 21:00:20 -0800 Subject: [PATCH 054/118] support sharrow rotation --- src/aframe-streetmix-parsers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 26d193947..e4b0feead 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -502,7 +502,7 @@ function processSegments( if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { segmentParentEl.setAttribute( 'street-generated-stencil', - `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15;` + `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15; facing: ${rotationY}` ); } else if ( segments[i].type === 'bike-lane' || From 47d4039ed2aeeffc27f53c1dab4deea2150322df Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 27 Nov 2024 10:07:44 -0800 Subject: [PATCH 055/118] basic striping working still need to replace insertSeparatorSegments --- src/aframe-streetmix-parsers.js | 85 +++++---------------- src/components/street-generated-striping.js | 79 +++++++++++++++++++ src/index.js | 1 + 3 files changed, 97 insertions(+), 68 deletions(-) create mode 100644 src/components/street-generated-striping.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index e4b0feead..4ab2047f4 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -380,44 +380,6 @@ function createCenteredStreetElement(segments) { return streetEl; } -function calculateHeight(elevation) { - const stepLevel = 0.15; - if (elevation <= 0) { - return stepLevel; - } - return stepLevel * (elevation + 1); -} - -function createSeparatorElement( - positionY, - rotationY, - mixinId, - length, - repeatCount, - elevation = 0 -) { - var segmentEl = document.createElement('a-entity'); - const scaleY = length / 150; - const scalePlane = '1 ' + scaleY + ' 1'; - - segmentEl.setAttribute('rotation', '270 ' + rotationY + ' 0'); - segmentEl.setAttribute('scale', scalePlane); - - let posY = calculateHeight(elevation) + positionY; - // take into account elevation property and add to positionY - segmentEl.setAttribute('position', '0 ' + posY + ' 0'); - segmentEl.setAttribute('mixin', mixinId); - - if (repeatCount.length !== 0) { - segmentEl.setAttribute( - 'material', - `repeat: ${repeatCount[0]} ${repeatCount[1]}` - ); - } - - return segmentEl; -} - // show warning message if segment or variantString are not supported function supportCheck(segmentType, segmentVariantString) { if (segmentType === 'separator') return; @@ -474,7 +436,6 @@ function processSegments( cumulativeWidthInMeters = cumulativeWidthInMeters + segmentWidthInMeters; var segmentPositionX = cumulativeWidthInMeters - 0.5 * segmentWidthInMeters; - var positionY = 0; // get variantString var variantList = segments[i].variantString @@ -495,9 +456,6 @@ function processSegments( // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that var segmentPreset = segments[i].type; - // repeat value for material property - repeatCount[0] is x texture repeat and repeatCount[1] is y texture repeat - const repeatCount = []; - // look at segment type and variant(s) to determine specific cases if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { segmentParentEl.setAttribute( @@ -577,7 +535,7 @@ function processSegments( if (variantList[1] === 'shared') { segmentParentEl.setAttribute( 'street-generated-stencil__2', - `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.3; spacing: 20; facing: ${rotationY + 180};` + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.6; spacing: 20; facing: ${rotationY + 180};` ); } } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { @@ -766,7 +724,6 @@ function processSegments( 'street-generated-fixed', `model: bikerack; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.2` ); - // add bike racks to the segment parent } else if (segments[i].type === 'magic-carpet') { segmentPreset = 'drive-lane'; segmentParentEl.setAttribute( @@ -872,43 +829,28 @@ function processSegments( variantList[0] === 'dashed' ) { segmentPreset = 'dashed-stripe'; - positionY = 0.01; // make sure the lane marker is above the asphalt - // for all markings material property repeat = "1 25". So every 150/25=6 meters put a dash - repeatCount[0] = 1; - repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'separator' && variantList[0] === 'solid') { segmentPreset = 'solid-stripe'; - positionY = 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'doubleyellow' ) { segmentPreset = 'solid-doubleyellow'; - positionY = 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'shortdashedyellow' ) { segmentPreset = 'short-dashed-stripe-yellow'; - positionY = 0.01; // make sure the lane marker is above the asphalt - // for short-dashed-stripe every 3 meters put a dash - repeatCount[0] = 1; - repeatCount[1] = parseInt(length / 3); } else if ( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellow' ) { segmentPreset = 'solid-dashed-yellow'; - positionY = 0.01; // make sure the lane marker is above the asphalt } else if ( segments[i].type === 'separator' && variantList[0] === 'soliddashedyellowinverted' ) { segmentPreset = 'solid-dashed-yellow'; - positionY = 0.01; // make sure the lane marker is above the asphalt - rotationY = '180'; - repeatCount[0] = 1; - repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'parking-lane') { segmentPreset = 'parking-lane'; let parkingMixin = 'stencils parking-t'; @@ -997,16 +939,23 @@ function processSegments( }; newManagedStreetDataStructureChildren.push(childData); } else { - segmentParentEl.append( - createSeparatorElement( - positionY, - rotationY, - segmentPreset, - length, - repeatCount, - elevation - ) + segmentParentEl.setAttribute( + 'street-generated-striping', + `striping: ${segmentPreset}; length: ${length};` // set facing 180 when in a shared turn lane ); + // if previous segment is turn lane and shared, then facing should be 180 + let previousSegment = segments[i - 1]; + if ( + previousSegment && + previousSegment.type === 'turn-lane' && + previousSegment.variantString.split('|')[1] === 'shared' + ) { + segmentParentEl.setAttribute( + 'street-generated-striping', + 'facing', + 180 + ); + } } // returns JSON output instead // append the new surfaceElement to the segmentParentEl diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js new file mode 100644 index 000000000..a3a5f5e50 --- /dev/null +++ b/src/components/street-generated-striping.js @@ -0,0 +1,79 @@ +/* global AFRAME */ + +// a-frame component to generate cloned models along a street +// this moves logic from aframe-streetmix-parsers into this component + +AFRAME.registerComponent('street-generated-striping', { + multiple: true, + schema: { + striping: { + type: 'string' + }, + side: { + default: 'left', + oneOf: ['left', 'right'] + }, + facing: { + default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 + type: 'number' + }, + length: { + // length in meters of linear path to fill with clones + type: 'number' + }, + positionX: { + // x position of clones along the length + default: 0, + type: 'number' + }, + positionY: { + // y position of clones along the length + default: 0.2, // this is too high, instead this should component should respect elevation to follow street segment + type: 'number' + } + }, + init: function () { + this.createdEntities = []; + }, + update: function (oldData) { + const data = this.data; + if (AFRAME.utils.deepEqual(oldData, data)) return; + + // Clean up old entities + this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities = []; + + const clone = document.createElement('a-entity'); + clone.setAttribute('mixin', data.striping); + + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: 0 + }); + + const scaleY = data.length / 150; + const scalePlane = '1 ' + scaleY + ' 1'; + + clone.setAttribute('scale', scalePlane); + + let repeatY = data.length / 6; + if (data.striping === 'short-dashed-stripe-yellow') { + repeatY = data.length / 3; + } + clone.setAttribute('rotation', { + x: -90, + y: data.facing, + z: 0 + }); + + clone.setAttribute('material', `repeat: 1 ${repeatY}`); + + clone.classList.add('autocreated'); + // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings + clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', 'Cloned Striping • ' + data.striping); + this.el.appendChild(clone); + this.createdEntities.push(clone); + } +}); diff --git a/src/index.js b/src/index.js index 78e85640d..1763421ae 100644 --- a/src/index.js +++ b/src/index.js @@ -26,6 +26,7 @@ require('./components/street-generated-fixed.js'); require('./components/street-generated-single.js'); require('./components/street-generated-random.js'); require('./components/street-generated-stencil.js'); +require('./components/street-generated-striping.js'); require('./editor/index.js'); const state = useStore.getState(); From 40e79f563d789544ec623641c72a4813c37a9c3b Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 27 Nov 2024 15:21:02 -0800 Subject: [PATCH 056/118] new striping logic --- src/aframe-streetmix-parsers.js | 279 ++++++++------------ src/assets.js | 8 +- src/components/street-generated-striping.js | 78 ++++-- 3 files changed, 176 insertions(+), 189 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 4ab2047f4..45f7b4fcf 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -47,11 +47,11 @@ const TYPES = { } }; -// this function takes a list of segments and adds lane markings or "separator segments" -// these are 0 width segments inserted into the street json prior to rendering -// the basic logic is: if there are two adjacent "lane-ish" segments, then add lane separators -function insertSeparatorSegments(segments) { - // first, let's define what is a lane that will likely need adajcent striping? +function getSeparatorMixinId(previousSegment, currentSegment) { + if (previousSegment === undefined || currentSegment === undefined) { + return null; + } + // Helper function to check if a segment type is "lane-ish" function isLaneIsh(typeString) { return ( typeString.slice(typeString.length - 4) === 'lane' || @@ -61,106 +61,81 @@ function insertSeparatorSegments(segments) { ); } - // then let's go through the segments array and build a new one with inserted separators - const newValues = segments.reduce( - (newArray, currentValue, currentIndex, arr) => { - // don't insert a lane marker before the first segment - if (currentIndex === 0) { - return newArray.concat(currentValue); - } + // If either segment is not lane-ish and not a divider, return null + if ( + (!isLaneIsh(previousSegment.type) && previousSegment.type !== 'divider') || + (!isLaneIsh(currentSegment.type) && currentSegment.type !== 'divider') + ) { + return null; + } - const previousValue = arr[currentIndex - 1]; - - // if both adjacent lanes are "laneish" - if (isLaneIsh(currentValue.type) && isLaneIsh(previousValue.type)) { - // if in doubt start with a solid line - var variantString = 'solid'; - - // if adjacent lane types are identical, then used dashed lines - if (currentValue.type === previousValue.type) { - variantString = 'dashed'; - } - - // Or, if either is a drive lane or turn lane then use dashed - // Using dash vs solid for turn lanes along approach to intersections may need to be user defined - if ( - (currentValue.type === 'drive-lane' && - previousValue.type === 'turn-lane') || - (previousValue.type === 'drive-lane' && - currentValue.type === 'turn-lane') - ) { - variantString = 'dashed'; - } - - // if adjacent segments in opposite directions then use double yellow - if ( - currentValue.variantString.split('|')[0] !== - previousValue.variantString.split('|')[0] - ) { - variantString = 'doubleyellow'; - // if adjacenet segments are both bike lanes, then use yellow short dash - if ( - currentValue.type === 'bike-lane' && - previousValue.type === 'bike-lane' - ) { - variantString = 'shortdashedyellow'; - } - if ( - currentValue.type === 'flex-zone' || - previousValue.type === 'flex-zone' - ) { - variantString = 'solid'; - } - } - - // special case -- if either lanes are turn lane shared, then use solid and long dash - if ( - currentValue.type === 'turn-lane' && - currentValue.variantString.split('|')[1] === 'shared' - ) { - variantString = 'soliddashedyellow'; - } else if ( - previousValue.type === 'turn-lane' && - previousValue.variantString.split('|')[1] === 'shared' - ) { - variantString = 'soliddashedyellowinverted'; - } - - // if adjacent to parking lane with markings, do not draw white line - if ( - currentValue.type === 'parking-lane' || - previousValue.type === 'parking-lane' - ) { - variantString = 'invisible'; - } - - newArray.push({ - type: 'separator', - variantString: variantString, - width: 0, - elevation: currentValue.elevation - }); - } + // Default to solid line + let variantString = 'solid-stripe'; - // if a *lane segment and divider are adjacent, use a solid separator - if ( - (isLaneIsh(currentValue.type) && previousValue.type === 'divider') || - (isLaneIsh(previousValue.type) && currentValue.type === 'divider') - ) { - newArray.push({ - type: 'separator', - variantString: 'solid', - width: 0, - elevation: currentValue.elevation - }); - } + // Handle divider cases + if (previousSegment.type === 'divider' || currentSegment.type === 'divider') { + return variantString; + } - newArray.push(currentValue); - return newArray; - }, - [] - ); - return newValues; + // Get directions from variant strings + const prevDirection = previousSegment.variantString.split('|')[0]; + const currDirection = currentSegment.variantString.split('|')[0]; + + // Check for opposite directions + if (prevDirection !== currDirection) { + variantString = 'solid-doubleyellow'; + + // Special case for bike lanes + if ( + currentSegment.type === 'bike-lane' && + previousSegment.type === 'bike-lane' + ) { + variantString = 'short-dashed-stripe-yellow'; + } + + // Special case for flex zones + if ( + currentSegment.type === 'flex-zone' || + previousSegment.type === 'flex-zone' + ) { + variantString = 'solid'; + } + } else { + // Same direction cases + if (currentSegment.type === previousSegment.type) { + variantString = 'dashed-stripe'; + } + + // Drive lane and turn lane combination + if ( + (currentSegment.type === 'drive-lane' && + previousSegment.type === 'turn-lane') || + (previousSegment.type === 'drive-lane' && + currentSegment.type === 'turn-lane') + ) { + variantString = 'dashed-stripe'; + } + } + + // Special cases for shared turn lanes + const prevVariant = previousSegment.variantString.split('|')[1]; + const currVariant = currentSegment.variantString.split('|')[1]; + + if (currentSegment.type === 'turn-lane' && currVariant === 'shared') { + variantString = 'solid-dashed-yellow'; + } else if (previousSegment.type === 'turn-lane' && prevVariant === 'shared') { + variantString = 'solid-dashed-yellow'; + } + + // Special case for parking lanes + if ( + currentSegment.type === 'parking-lane' || + previousSegment.type === 'parking-lane' + ) { + variantString = 'invisible'; + } + + return variantString; } function createRailsElement(length, railsPosX) { @@ -411,11 +386,6 @@ function processSegments( globalAnimated, showVehicles ) { - // add additional 0-width segments for stripes (painted markers) - if (showStriping) { - segments = insertSeparatorSegments(segments); - } - // create and center offset to center the street around global x position of 0 var streetParentEl = createCenteredStreetElement(segments); streetParentEl.classList.add('street-parent'); @@ -824,33 +794,6 @@ function processSegments( 'street-generated-single', `model: brt-station; length: ${length};` ); - } else if ( - segments[i].type === 'separator' && - variantList[0] === 'dashed' - ) { - segmentPreset = 'dashed-stripe'; - } else if (segments[i].type === 'separator' && variantList[0] === 'solid') { - segmentPreset = 'solid-stripe'; - } else if ( - segments[i].type === 'separator' && - variantList[0] === 'doubleyellow' - ) { - segmentPreset = 'solid-doubleyellow'; - } else if ( - segments[i].type === 'separator' && - variantList[0] === 'shortdashedyellow' - ) { - segmentPreset = 'short-dashed-stripe-yellow'; - } else if ( - segments[i].type === 'separator' && - variantList[0] === 'soliddashedyellow' - ) { - segmentPreset = 'solid-dashed-yellow'; - } else if ( - segments[i].type === 'separator' && - variantList[0] === 'soliddashedyellowinverted' - ) { - segmentPreset = 'solid-dashed-yellow'; } else if (segments[i].type === 'parking-lane') { segmentPreset = 'parking-lane'; let parkingMixin = 'stencils parking-t'; @@ -910,41 +853,39 @@ function processSegments( if (streetmixParsersTested.isSidewalk(segments[i].type)) { segmentPreset = 'sidewalk'; } + // add new object - if (segments[i].type !== 'separator') { - segmentParentEl.setAttribute('street-segment', 'type', segmentPreset); - segmentParentEl.setAttribute( - 'street-segment', - 'width', - segmentWidthInMeters - ); - segmentParentEl.setAttribute('street-segment', 'length', length); - segmentParentEl.setAttribute('street-segment', 'elevation', elevation); - segmentParentEl.setAttribute( - 'street-segment', - 'color', - segmentColor ?? TYPES[segmentPreset]?.color - ); - segmentParentEl.setAttribute( - 'street-segment', - 'surface', - TYPES[segmentPreset]?.surface - ); - // experimental - output the new data structure - let childData = { - id: segments[i].id, // this will collide with other segment ID if there are multiple streets placed with identical segment id's - type: segments[i].type, - width: segmentWidthInMeters, - elevation: elevation - }; - newManagedStreetDataStructureChildren.push(childData); - } else { + segmentParentEl.setAttribute('street-segment', 'type', segmentPreset); + segmentParentEl.setAttribute( + 'street-segment', + 'width', + segmentWidthInMeters + ); + segmentParentEl.setAttribute('street-segment', 'length', length); + segmentParentEl.setAttribute('street-segment', 'elevation', elevation); + segmentParentEl.setAttribute( + 'street-segment', + 'color', + segmentColor ?? TYPES[segmentPreset]?.color + ); + segmentParentEl.setAttribute( + 'street-segment', + 'surface', + TYPES[segmentPreset]?.surface + ); + + let currentSegment = segments[i]; + let previousSegment = segments[i - 1]; + let separatorMixinId = getSeparatorMixinId(previousSegment, currentSegment); + + console.log('separatorMixinId', separatorMixinId); + + if (separatorMixinId && showStriping) { segmentParentEl.setAttribute( 'street-generated-striping', - `striping: ${segmentPreset}; length: ${length};` // set facing 180 when in a shared turn lane + `striping: ${separatorMixinId}; length: ${length}; segmentWidth: ${segmentWidthInMeters};` ); // if previous segment is turn lane and shared, then facing should be 180 - let previousSegment = segments[i - 1]; if ( previousSegment && previousSegment.type === 'turn-lane' && @@ -957,6 +898,16 @@ function processSegments( ); } } + + // experimental - output the new data structure + let childData = { + id: segments[i].id, // this will collide with other segment ID if there are multiple streets placed with identical segment id's + type: segments[i].type, + width: segmentWidthInMeters, + elevation: elevation + }; + newManagedStreetDataStructureChildren.push(childData); + // returns JSON output instead // append the new surfaceElement to the segmentParentEl streetParentEl.append(segmentParentEl); diff --git a/src/assets.js b/src/assets.js index 69f48013c..dae3c74ad 100644 --- a/src/assets.js +++ b/src/assets.js @@ -140,7 +140,13 @@ function buildAssetHTML(assetUrl, categories) { `, 'lane-separator': ` - + + + + + + + diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index a3a5f5e50..6f6680534 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -9,6 +9,9 @@ AFRAME.registerComponent('street-generated-striping', { striping: { type: 'string' }, + segmentWidth: { + type: 'number' + }, side: { default: 'left', oneOf: ['left', 'right'] @@ -21,14 +24,9 @@ AFRAME.registerComponent('street-generated-striping', { // length in meters of linear path to fill with clones type: 'number' }, - positionX: { - // x position of clones along the length - default: 0, - type: 'number' - }, positionY: { // y position of clones along the length - default: 0.2, // this is too high, instead this should component should respect elevation to follow street segment + default: 0.05, // this is too high, instead this should component should respect elevation to follow street segment type: 'number' } }, @@ -42,38 +40,70 @@ AFRAME.registerComponent('street-generated-striping', { // Clean up old entities this.createdEntities.forEach((entity) => entity.remove()); this.createdEntities = []; - + if (data.striping === 'invisible') { + return; + } const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', data.striping); - + const { stripingTextureId, repeatY, color, stripingWidth } = + this.calculateStripingMaterial(data.striping, data.length); + const positionX = ((data.side === 'left' ? -1 : 1) * data.segmentWidth) / 2; clone.setAttribute('position', { - x: data.positionX, + x: positionX, y: data.positionY, z: 0 }); - - const scaleY = data.length / 150; - const scalePlane = '1 ' + scaleY + ' 1'; - - clone.setAttribute('scale', scalePlane); - - let repeatY = data.length / 6; - if (data.striping === 'short-dashed-stripe-yellow') { - repeatY = data.length / 3; - } clone.setAttribute('rotation', { x: -90, y: data.facing, z: 0 }); - - clone.setAttribute('material', `repeat: 1 ${repeatY}`); - + clone.setAttribute( + 'material', + `src: #${stripingTextureId}; alphaTest: 0; transparent:true; repeat:1 ${repeatY}; color: ${color}` + ); + clone.setAttribute( + 'geometry', + `primitive: plane; width: ${stripingWidth}; height: ${data.length}; skipCache: true;` + ); clone.classList.add('autocreated'); // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings clone.setAttribute('data-no-transform', ''); - clone.setAttribute('data-layer-name', 'Cloned Striping • ' + data.striping); + clone.setAttribute( + 'data-layer-name', + 'Cloned Striping • ' + stripingTextureId + ); this.el.appendChild(clone); this.createdEntities.push(clone); + }, + calculateStripingMaterial: function (stripingName, length) { + // calculate the repeatCount for the material + var stripingTextureId = 'striping-solid-stripe'; // drive-lane, bus-lane, bike-lane + var repeatY = length / 6; + var color = '#FFFFFF'; // we could get rid of this using cropped texture for asphalt + var stripingWidth = 0.2; + if (stripingName === 'solid-stripe') { + stripingTextureId = 'striping-solid-stripe'; + } else if (stripingName === 'dashed-stripe') { + stripingTextureId = 'striping-dashed-stripe'; + } else if (stripingName === 'short-dashed-stripe') { + stripingTextureId = 'striping-dashed-stripe'; + repeatY = length / 3; + } else if (stripingName === 'short-dashed-stripe-yellow') { + stripingTextureId = 'striping-dashed-stripe'; + repeatY = length / 3; + color = '#f7d117'; + } else if (stripingName === 'solid-doubleyellow') { + stripingTextureId = 'striping-solid-double'; + stripingWidth = 0.5; + color = '#f7d117'; + } else if (stripingName === 'solid-dashed') { + stripingTextureId = 'striping-solid-dashed'; + stripingWidth = 0.4; + } else if (stripingName === 'solid-dashed-yellow') { + stripingTextureId = 'striping-solid-dashed'; + color = '#f7d117'; + stripingWidth = 0.4; + } + return { stripingTextureId, repeatY, color, stripingWidth }; } }); From 56a27b18236975a2bb49fb3d31dfb9bb9cde3993 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 27 Nov 2024 21:44:51 -0800 Subject: [PATCH 057/118] remove animation --- src/aframe-streetmix-parsers.js | 49 --------------------------------- 1 file changed, 49 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 45f7b4fcf..7104c48ae 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -237,25 +237,13 @@ function createSidewalkClonedVariants( const yVal = 0; // y = 0.2 for sidewalk elevation const placedObjectEl = document.createElement('a-entity'); - let animationDirection = 'inbound'; placedObjectEl.setAttribute('position', { x: xVal, y: yVal, z: zVal }); placedObjectEl.setAttribute('mixin', variantName); // Roughly 50% of traffic will be incoming if (Math.random() < 0.5 && direction === 'random') { placedObjectEl.setAttribute('rotation', '0 180 0'); - animationDirection = 'outbound'; } - if (animated) { - addLinearStreetAnimation( - placedObjectEl, - 1.4, - streetLength, - xVal, - zVal, - animationDirection - ); - } dividerParentEl.append(placedObjectEl); } @@ -275,43 +263,6 @@ function getSegmentColor(variant) { return COLORS.white; } -function addLinearStreetAnimation( - reusableObjectEl, - speed, - streetLength, - xPos, - zPos, - direction -) { - const totalStreetDuration = (streetLength / speed) * 1000; // time in milliseconds - const halfStreet = - direction === 'outbound' ? -streetLength / 2 : streetLength / 2; - const startingDistanceToTravel = Math.abs(halfStreet - zPos); - const startingDuration = (startingDistanceToTravel / speed) * 1000; - - const animationAttrs1 = { - property: 'position', - easing: 'linear', - loop: 'false', - from: { x: xPos, y: 0, z: zPos }, - to: { z: halfStreet }, - dur: startingDuration - }; - const animationAttrs2 = { - property: 'position', - easing: 'linear', - loop: 'true', - from: { x: xPos, y: 0, z: -halfStreet }, - to: { x: xPos, y: 0, z: halfStreet }, - delay: startingDuration, - dur: totalStreetDuration - }; - reusableObjectEl.setAttribute('animation__1', animationAttrs1); - reusableObjectEl.setAttribute('animation__2', animationAttrs2); - - return reusableObjectEl; -} - function createWayfindingElements() { const wayfindingParentEl = document.createElement('a-entity'); let reusableObjectEl; From 5fd4f0df1e270d395cc890e72b48c83506082c52 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 27 Nov 2024 22:06:23 -0800 Subject: [PATCH 058/118] color comment fix --- src/components/street-generated-striping.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 6f6680534..cdc28953d 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -79,7 +79,7 @@ AFRAME.registerComponent('street-generated-striping', { // calculate the repeatCount for the material var stripingTextureId = 'striping-solid-stripe'; // drive-lane, bus-lane, bike-lane var repeatY = length / 6; - var color = '#FFFFFF'; // we could get rid of this using cropped texture for asphalt + var color = '#ffffff'; var stripingWidth = 0.2; if (stripingName === 'solid-stripe') { stripingTextureId = 'striping-solid-stripe'; From 8da1ccb1f0389c783ab3028a24c9fa26af0e51b2 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 27 Nov 2024 22:06:44 -0800 Subject: [PATCH 059/118] convert pedestrians removed animation capabilities --- src/aframe-streetmix-parsers.js | 77 +---------- .../street-generated-pedestrians.js | 129 ++++++++++++++++++ src/index.js | 1 + 3 files changed, 133 insertions(+), 74 deletions(-) create mode 100644 src/components/street-generated-pedestrians.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 7104c48ae..12035a686 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -186,70 +186,6 @@ function getRandomIntInclusive(min, max) { return Math.floor(Math.random() * (max - min + 1) + min); } -function getRandomArbitrary(min, max) { - return Math.random() * (max - min) + min; -} - -function getZPositions(start, end, step) { - const len = Math.floor((end - start) / step) + 1; - var arr = Array(len) - .fill() - .map((_, idx) => start + idx * step); - return arr.sort(() => 0.5 - Math.random()); -} - -function createSidewalkClonedVariants( - segmentWidthInMeters, - density, - streetLength, - direction = 'random', - animated = false -) { - const xValueRange = [ - -(0.37 * segmentWidthInMeters), - 0.37 * segmentWidthInMeters - ]; - const zValueRange = getZPositions( - -0.5 * streetLength, - 0.5 * streetLength, - 1.5 - ); - const densityFactors = { - empty: 0, - sparse: 0.03, - normal: 0.125, - dense: 0.25 - }; - const totalPedestrianNumber = parseInt( - densityFactors[density] * streetLength, - 10 - ); - const dividerParentEl = document.createElement('a-entity'); - dividerParentEl.setAttribute('data-layer-name', 'Pedestrians Parent'); - // Randomly generate avatars - for (let i = 0; i < totalPedestrianNumber; i++) { - const variantName = - animated === true - ? 'a_char' + String(getRandomIntInclusive(1, 8)) - : 'char' + String(getRandomIntInclusive(1, 16)); - const xVal = getRandomArbitrary(xValueRange[0], xValueRange[1]); - const zVal = zValueRange.pop(); - const yVal = 0; - // y = 0.2 for sidewalk elevation - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('position', { x: xVal, y: yVal, z: zVal }); - placedObjectEl.setAttribute('mixin', variantName); - // Roughly 50% of traffic will be incoming - if (Math.random() < 0.5 && direction === 'random') { - placedObjectEl.setAttribute('rotation', '0 180 0'); - } - - dividerParentEl.append(placedObjectEl); - } - - return dividerParentEl; -} - function getSegmentColor(variant) { if ((variant === 'red') | (variant === 'colored')) { return COLORS.red; @@ -612,16 +548,9 @@ function processSegments( `stencils: word-loading-small, word-only-small; length: ${length}; spacing: 40; padding: 10; facing: ${rotationY}` ); } else if (segments[i].type === 'sidewalk' && variantList[0] !== 'empty') { - // handles variantString with value sparse, normal, or dense sidewalk - const isAnimated = variantList[1] === 'animated' || globalAnimated; - segmentParentEl.append( - createSidewalkClonedVariants( - segmentWidthInMeters, - variantList[0], - length, - 'random', - isAnimated - ) + segmentParentEl.setAttribute( + 'street-generated-pedestrians', + `segmentWidth: ${segmentWidthInMeters}; density: ${variantList[0]}; length: ${length}; direction: random;` ); } else if (segments[i].type === 'sidewalk-wayfinding') { segmentParentEl.append(createWayfindingElements()); diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js new file mode 100644 index 000000000..33e952073 --- /dev/null +++ b/src/components/street-generated-pedestrians.js @@ -0,0 +1,129 @@ +/* global AFRAME */ + +// a-frame component to generate cloned pedestrian models along a street +AFRAME.registerComponent('street-generated-pedestrians', { + multiple: true, + schema: { + segmentWidth: { + // width of the segment in meters + type: 'number', + default: 3 + }, + density: { + type: 'string', + default: 'normal', + oneOf: ['empty', 'sparse', 'normal', 'dense'] + }, + length: { + // length in meters of linear path to fill with clones + type: 'number' + }, + direction: { + type: 'string', + default: 'random', + oneOf: ['random', 'inbound', 'outbound'] + }, + // animated: { + // // load 8 animated characters instead of 16 static characters + // type: 'boolean', + // default: false + // }, + positionY: { + // y position of pedestrians + type: 'number', + default: 0 + } + }, + + init: function () { + this.createdEntities = []; + this.densityFactors = { + empty: 0, + sparse: 0.03, + normal: 0.125, + dense: 0.25 + }; + }, + + update: function (oldData) { + const data = this.data; + if (AFRAME.utils.deepEqual(oldData, data)) return; + + // Clean up old entities + this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities = []; + + // Calculate x position range based on segment width + const xRange = { + min: -(0.37 * data.segmentWidth), + max: 0.37 * data.segmentWidth + }; + + // Calculate total number of pedestrians based on density and street length + const totalPedestrians = Math.floor( + this.densityFactors[data.density] * data.length + ); + + // Get available z positions + const zPositions = this.getZPositions( + -data.length / 2, + data.length / 2, + 1.5 + ); + + // Create pedestrians + for (let i = 0; i < totalPedestrians; i++) { + const pedestrian = document.createElement('a-entity'); + + // Set random position within bounds + const position = { + x: this.getRandomArbitrary(xRange.min, xRange.max), + y: data.positionY, + z: zPositions.pop() + }; + pedestrian.setAttribute('position', position); + + // Set model variant + const variantNumber = this.getRandomIntInclusive( + 1, + data.animated ? 8 : 16 + ); + const variantPrefix = data.animated ? 'a_char' : 'char'; + pedestrian.setAttribute('mixin', `${variantPrefix}${variantNumber}`); + + // Set rotation based on direction + if (data.direction === 'random' && Math.random() < 0.5) { + pedestrian.setAttribute('rotation', '0 180 0'); + } else if (data.direction === 'outbound') { + pedestrian.setAttribute('rotation', '0 180 0'); + } + + // Add metadata + pedestrian.classList.add('autocreated'); + pedestrian.setAttribute('data-no-transform', ''); + pedestrian.setAttribute('data-layer-name', 'Generated Pedestrian'); + + this.el.appendChild(pedestrian); + this.createdEntities.push(pedestrian); + } + }, + + // Helper methods from legacy function + getRandomIntInclusive: function (min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); + }, + + getRandomArbitrary: function (min, max) { + return Math.random() * (max - min) + min; + }, + + getZPositions: function (start, end, step) { + const len = Math.floor((end - start) / step) + 1; + const arr = Array(len) + .fill() + .map((_, idx) => start + idx * step); + return arr.sort(() => 0.5 - Math.random()); + } +}); diff --git a/src/index.js b/src/index.js index 1763421ae..ae64ede5e 100644 --- a/src/index.js +++ b/src/index.js @@ -27,6 +27,7 @@ require('./components/street-generated-single.js'); require('./components/street-generated-random.js'); require('./components/street-generated-stencil.js'); require('./components/street-generated-striping.js'); +require('./components/street-generated-pedestrians.js'); require('./editor/index.js'); const state = useStore.getState(); From 53bb7f3e020be633fab16488003ccdcee761eefe Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 30 Nov 2024 20:04:02 -0800 Subject: [PATCH 060/118] fix brt-lane case --- src/aframe-streetmix-parsers.js | 7 +++---- src/segments-variants.js | 10 +++++++++- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 12035a686..3cd3f0e52 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -498,6 +498,7 @@ function processSegments( segments[i].type === 'bus-lane' || segments[i].type === 'brt-lane' ) { + segmentPreset = 'bus-lane'; // get the color for a bus lane segmentColor = getSegmentColor(variantList[1]); @@ -746,20 +747,18 @@ function processSegments( segmentParentEl.setAttribute( 'street-segment', 'color', - segmentColor ?? TYPES[segmentPreset]?.color + segmentColor ?? TYPES[segmentPreset]?.color // no error handling for segmentPreset not found ); segmentParentEl.setAttribute( 'street-segment', 'surface', - TYPES[segmentPreset]?.surface + TYPES[segmentPreset]?.surface // no error handling for segmentPreset not found ); let currentSegment = segments[i]; let previousSegment = segments[i - 1]; let separatorMixinId = getSeparatorMixinId(previousSegment, currentSegment); - console.log('separatorMixinId', separatorMixinId); - if (separatorMixinId && showStriping) { segmentParentEl.setAttribute( 'street-generated-striping', diff --git a/src/segments-variants.js b/src/segments-variants.js index 5680c6ec4..4d5afd173 100644 --- a/src/segments-variants.js +++ b/src/segments-variants.js @@ -59,6 +59,14 @@ const segmentVariants = { 'inbound|red|typical', 'outbound|red|typical' ], + 'brt-lane': [ + 'inbound|colored', + 'outbound|colored', + 'inbound|regular', + 'outbound|regular', + 'inbound|red', + 'outbound|red' + ], 'drive-lane': [ 'inbound|car', 'outbound|car', @@ -134,7 +142,7 @@ const segmentVariants = { 'outbound|grass' ], // stations - 'brt-station': ['center'], + 'brt-station': ['center', 'left', 'right'], 'transit-shelter': [ 'left|street-level', 'right|street-level', From 46b387ccb689694507fd2020dc7f4b0a065903bd Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 2 Dec 2024 09:59:17 -0800 Subject: [PATCH 061/118] create rails --- src/aframe-streetmix-parsers.js | 50 ++----------------- src/components/street-generated-rail.js | 64 +++++++++++++++++++++++++ src/index.js | 1 + 3 files changed, 69 insertions(+), 46 deletions(-) create mode 100644 src/components/street-generated-rail.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 3cd3f0e52..b3ed94410 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -138,48 +138,6 @@ function getSeparatorMixinId(previousSegment, currentSegment) { return variantString; } -function createRailsElement(length, railsPosX) { - const placedObjectEl = document.createElement('a-entity'); - const railsGeometry = { - primitive: 'box', - depth: length, - width: 0.1, - height: 0.2 - }; - const railsMaterial = { - // TODO: Add environment map for reflection on metal rails - color: '#8f8f8f', - metalness: 1, - emissive: '#828282', - emissiveIntensity: 0.5, - roughness: 0.1 - }; - placedObjectEl.setAttribute('geometry', railsGeometry); - placedObjectEl.setAttribute('material', railsMaterial); - placedObjectEl.setAttribute('data-layer-name', 'rails'); - placedObjectEl.setAttribute('shadow', 'receive:true; cast: true'); - placedObjectEl.setAttribute('position', railsPosX + ' 0.2 0'); // position="1.043 0.100 -3.463" - - return placedObjectEl; -} - -function createTracksParentElement(length, objectMixinId) { - const placedObjectEl = document.createElement('a-entity'); - placedObjectEl.setAttribute('data-layer-name', 'Tracks Parent'); - placedObjectEl.setAttribute('position', '0 -0.2 0'); // position="1.043 0.100 -3.463" - // add rails - const railsWidth = { - // width as measured from center of rail, so 1/2 actual width - tram: 0.7175, // standard gauge 1,435 mm - trolley: 0.5335 // sf cable car rail gauge 1,067 mm - }; - const railsPosX = railsWidth[objectMixinId]; - placedObjectEl.append(createRailsElement(length, railsPosX)); - placedObjectEl.append(createRailsElement(length, -railsPosX)); - - return placedObjectEl; -} - function getRandomIntInclusive(min, max) { min = Math.ceil(min); max = Math.floor(max); @@ -355,10 +313,10 @@ function processSegments( `model: ${objectMixinId}; length: ${length}; placeLength: 23; facing: ${rotationY}; count: 1;` ); } - // make the parent for all the objects to be cloned - const tracksParentEl = createTracksParentElement(length, objectMixinId); - // add these trains to the segment parent - segmentParentEl.append(tracksParentEl); + segmentParentEl.setAttribute( + 'street-generated-rail', + `length: ${length}; gauge: ${segments[i].type === 'streetcar' ? 1067 : 1435};` + ); } else if (segments[i].type === 'turn-lane') { segmentPreset = 'drive-lane'; // use normal drive lane road material if (showVehicles && variantList[1] !== 'shared') { diff --git a/src/components/street-generated-rail.js b/src/components/street-generated-rail.js new file mode 100644 index 000000000..d4b294bc8 --- /dev/null +++ b/src/components/street-generated-rail.js @@ -0,0 +1,64 @@ +/* global AFRAME */ + +AFRAME.registerComponent('street-generated-rail', { + schema: { + length: { + // length in meters of linear path to fill with rail + type: 'number' + }, + gauge: { + // spacing in millimeters between rails + type: 'int', + default: 1435, // standard gauge in mm + oneOf: [1435, 1067] + } + }, + init: function () { + this.createdEntities = []; + }, + update: function (oldData) { + const data = this.data; + if (AFRAME.utils.deepEqual(oldData, data)) return; + + // Clean up old entities + this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities = []; + + const clone = document.createElement('a-entity'); + clone.setAttribute('data-layer-name', 'Cloned Railroad Tracks'); + clone.setAttribute('position', '0 -0.2 0'); + const railsPosX = this.data.gauge / 2 / 1000; + clone.append(this.createRailsElement(this.data.length, railsPosX)); + clone.append(this.createRailsElement(this.data.length, -railsPosX)); + clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-ignore-raycaster', ''); + + this.el.appendChild(clone); + this.createdEntities.push(clone); + }, + createRailsElement: function (length, railsPosX) { + const placedObjectEl = document.createElement('a-entity'); + const railsGeometry = { + primitive: 'box', + depth: length, + width: 0.1, + height: 0.2 + }; + const railsMaterial = { + // TODO: Add environment map for reflection on metal rails + color: '#8f8f8f', + metalness: 1, + emissive: '#828282', + emissiveIntensity: 0.5, + roughness: 0.1 + }; + placedObjectEl.setAttribute('geometry', railsGeometry); + placedObjectEl.setAttribute('material', railsMaterial); + placedObjectEl.setAttribute('data-layer-name', 'Rail'); + placedObjectEl.setAttribute('data-no-transform', ''); + placedObjectEl.setAttribute('data-ignore-raycaster', ''); + placedObjectEl.setAttribute('position', railsPosX + ' 0.2 0'); // position="1.043 0.100 -3.463" + + return placedObjectEl; + } +}); diff --git a/src/index.js b/src/index.js index ae64ede5e..7acc06a3a 100644 --- a/src/index.js +++ b/src/index.js @@ -28,6 +28,7 @@ require('./components/street-generated-random.js'); require('./components/street-generated-stencil.js'); require('./components/street-generated-striping.js'); require('./components/street-generated-pedestrians.js'); +require('./components/street-generated-rail.js'); require('./editor/index.js'); const state = useStore.getState(); From f29ee5bcf5c83fde4c8c0ab27524ec974c9a833a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 2 Dec 2024 12:03:30 -0800 Subject: [PATCH 062/118] add wayfinding --- src/aframe-streetmix-parsers.js | 36 ++++----------------------------- src/assets.js | 1 + 2 files changed, 5 insertions(+), 32 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index b3ed94410..6ad099fc2 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -157,37 +157,6 @@ function getSegmentColor(variant) { return COLORS.white; } -function createWayfindingElements() { - const wayfindingParentEl = document.createElement('a-entity'); - let reusableObjectEl; - - reusableObjectEl = document.createElement('a-entity'); - reusableObjectEl.setAttribute('position', '0 1 0'); - reusableObjectEl.setAttribute('mixin', 'wayfinding-box'); - wayfindingParentEl.append(reusableObjectEl); - - reusableObjectEl = document.createElement('a-entity'); - reusableObjectEl.setAttribute('position', '0 1.2 0.06'); - reusableObjectEl.setAttribute( - 'geometry', - 'primitive: plane; width: 0.8; height: 1.6' - ); - reusableObjectEl.setAttribute('material', 'src:#wayfinding-map'); - wayfindingParentEl.append(reusableObjectEl); - - reusableObjectEl = document.createElement('a-entity'); - reusableObjectEl.setAttribute('position', '0 1.2 -0.06'); - reusableObjectEl.setAttribute('rotation', '0 180 0'); - reusableObjectEl.setAttribute( - 'geometry', - 'primitive: plane; width: 0.8; height: 1.6' - ); - reusableObjectEl.setAttribute('material', 'src:#wayfinding-map'); - wayfindingParentEl.append(reusableObjectEl); - - return wayfindingParentEl; -} - // offset to center the street around global x position of 0 function createCenteredStreetElement(segments) { const streetEl = document.createElement('a-entity'); @@ -512,7 +481,10 @@ function processSegments( `segmentWidth: ${segmentWidthInMeters}; density: ${variantList[0]}; length: ${length}; direction: random;` ); } else if (segments[i].type === 'sidewalk-wayfinding') { - segmentParentEl.append(createWayfindingElements()); + segmentParentEl.setAttribute( + 'street-generated-single', + `model: wayfinding; length: ${length};` + ); } else if (segments[i].type === 'sidewalk-bench') { const rotationCloneY = variantList[0] === 'right' ? -90 : 90; if (variantList[0] === 'center') { diff --git a/src/assets.js b/src/assets.js index dae3c74ad..a389662c1 100644 --- a/src/assets.js +++ b/src/assets.js @@ -36,6 +36,7 @@ function buildAssetHTML(assetUrl, categories) { + `, people: ` From 2b2be80141fb90ddc0a82cc6d8392809c71d5b27 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 2 Dec 2024 13:55:46 -0800 Subject: [PATCH 063/118] fix right vehicle parking orientation --- src/aframe-streetmix-parsers.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 6ad099fc2..99eb4d3ea 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -637,15 +637,18 @@ function processSegments( markingLength = segmentWidthInMeters; markingPosX = 0; parkingMixin = 'solid-stripe'; + if (variantList[1] === 'right') { + // make sure cars face the right way on right side + markingsRotZ = markingsRotZ + 180; + } } - segmentParentEl.setAttribute( 'street-generated-random', `modelsArray: sedan-rig, self-driving-waymo-car, suv-rig; length: ${length}; placeLength: ${carStep}; count: ${getRandomIntInclusive(6, 8)}; - facing: ${markingsRotZ - 90};` // this needs work -- the rotation is off by 180 degrees on the right side for perpendicular and angled variants + facing: ${markingsRotZ - 90};` ); if (variantList[1] === 'left') { segmentParentEl.setAttribute( From c60d62fa5ef542bd0921e56527ee1a1c42f7bfca Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 2 Dec 2024 20:28:41 -0800 Subject: [PATCH 064/118] consolidate -fixed and -single into street-generated-clones --- src/aframe-streetmix-parsers.js | 72 +++++------ src/components/street-generated-clones.js | 148 ++++++++++++++++++++++ src/components/street-generated-fixed.js | 100 --------------- src/components/street-generated-single.js | 90 ------------- src/index.js | 3 +- 5 files changed, 185 insertions(+), 228 deletions(-) create mode 100644 src/components/street-generated-clones.js delete mode 100644 src/components/street-generated-fixed.js delete mode 100644 src/components/street-generated-single.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 99eb4d3ea..572c6dae0 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -326,13 +326,13 @@ function processSegments( segmentPreset = 'divider'; // make some bollards segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: bollard; spacing: 4; length: ${length}` ); } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { segmentPreset = 'grass'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: dividers-flowers; spacing: 2.25; length: ${length}` ); } else if ( @@ -341,7 +341,7 @@ function processSegments( ) { segmentPreset = 'grass'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: dividers-planting-strip; spacing: 2.25; length: ${length}` ); } else if ( @@ -350,7 +350,7 @@ function processSegments( ) { segmentPreset = 'grass'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: dividers-planter-box; spacing: 2.45; length: ${length}` ); } else if ( @@ -359,7 +359,7 @@ function processSegments( ) { segmentPreset = 'grass'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: palm-tree; length: ${length}` ); } else if ( @@ -368,19 +368,19 @@ function processSegments( ) { segmentPreset = 'grass'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: tree3; length: ${length}` ); } else if (segments[i].type === 'divider' && variantList[0] === 'bush') { segmentPreset = 'grass'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: dividers-bush; spacing: 2.25; length: ${length}` ); } else if (segments[i].type === 'divider' && variantList[0] === 'dome') { segmentPreset = 'divider'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: dividers-dome; spacing: 2.25; length: ${length}` ); } else if (segments[i].type === 'divider') { @@ -391,7 +391,7 @@ function processSegments( ) { segmentPreset = 'drive-lane'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: temporary-barricade; spacing: 2.25; length: ${length}` ); } else if ( @@ -400,7 +400,7 @@ function processSegments( ) { segmentPreset = 'drive-lane'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: temporary-traffic-cone; spacing: 2.25; length: ${length}` ); } else if ( @@ -409,7 +409,7 @@ function processSegments( ) { segmentPreset = 'drive-lane'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: jersey-barrier-plastic; spacing: 2.25; length: ${length}` ); } else if ( @@ -418,7 +418,7 @@ function processSegments( ) { segmentPreset = 'drive-lane'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: temporary-jersey-barrier-concrete; spacing: 2.93; length: ${length}` ); } else if ( @@ -482,40 +482,40 @@ function processSegments( ); } else if (segments[i].type === 'sidewalk-wayfinding') { segmentParentEl.setAttribute( - 'street-generated-single', - `model: wayfinding; length: ${length};` + 'street-generated-clones', + `mode: single; model: wayfinding; length: ${length};` ); } else if (segments[i].type === 'sidewalk-bench') { const rotationCloneY = variantList[0] === 'right' ? -90 : 90; if (variantList[0] === 'center') { segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: bench_orientation_center; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` ); } else { // `right` or `left` bench segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: bench; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` ); } } else if (segments[i].type === 'sidewalk-bike-rack') { const rotationCloneY = variantList[1] === 'sidewalk-parallel' ? 90 : 0; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: bikerack; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.2` ); } else if (segments[i].type === 'magic-carpet') { segmentPreset = 'drive-lane'; segmentParentEl.setAttribute( - 'street-generated-single', - `model: magic-carpet; + 'street-generated-clones', + `mode: single; model: magic-carpet; length: ${length}; positionY: 1.2;` ); segmentParentEl.setAttribute( - 'street-generated-single__2', - `model: Character_1_M; + 'street-generated-clones__2', + `mode: single; model: Character_1_M; length: ${length}; positionY: 1.2;` ); @@ -535,20 +535,20 @@ function processSegments( } else if (segments[i].type === 'bikeshare') { const rotationCloneY = variantList[0] === 'left' ? 90 : 270; segmentParentEl.setAttribute( - 'street-generated-single', - `model: bikeshare; length: ${length}; facing: ${rotationCloneY}; justify: middle;` + 'street-generated-clones', + `mode: single; model: bikeshare; length: ${length}; facing: ${rotationCloneY}; justify: middle;` ); } else if (segments[i].type === 'utilities') { const rotationCloneY = variantList[0] === 'right' ? 180 : 0; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotationCloneY}` ); } else if (segments[i].type === 'sidewalk-tree') { const objectMixinId = variantList[0] === 'palm-tree' ? 'palm-tree' : 'tree3'; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: ${objectMixinId}; length: ${length}; randomFacing: true;` ); } else if ( @@ -557,13 +557,13 @@ function processSegments( ) { if (variantList[0] === 'both') { segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: lamp-modern-double; length: ${length}; cycleOffset: 0.4;` ); } else { var rotationCloneY = variantList[0] === 'right' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: lamp-modern; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.4;` ); } @@ -573,7 +573,7 @@ function processSegments( (variantList[0] === 'right' || variantList[0] === 'both') ) { segmentParentEl.setAttribute( - 'street-generated-fixed__2', + 'street-generated-clones__2', `model: pride-flag; length: ${length}; cycleOffset: 0.4; positionX: 0.409; positionY: 5;` ); } @@ -582,7 +582,7 @@ function processSegments( (variantList[0] === 'left' || variantList[0] === 'both') ) { segmentParentEl.setAttribute( - 'street-generated-fixed__2', + 'street-generated-clones__2', `model: pride-flag; length: ${length}; facing: 180; cycleOffset: 0.4; positionX: -0.409; positionY: 5;` ); } @@ -591,19 +591,19 @@ function processSegments( variantList[1] === 'traditional' ) { segmentParentEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: lamp-traditional; length: ${length};` ); } else if (segments[i].type === 'transit-shelter') { var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; segmentParentEl.setAttribute( - 'street-generated-single', - `model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` + 'street-generated-clones', + `mode: single; model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` ); } else if (segments[i].type === 'brt-station') { segmentParentEl.setAttribute( - 'street-generated-single', - `model: brt-station; length: ${length};` + 'street-generated-clones', + `mode: single; model: brt-station; length: ${length};` ); } else if (segments[i].type === 'parking-lane') { segmentPreset = 'parking-lane'; @@ -904,7 +904,7 @@ function processBuildings(left, right, streetWidth, showGround, length) { } placedObjectEl.setAttribute('data-layer-name', 'seawall-parent-' + side); placedObjectEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: seawall; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.8;` ); buildingElement.appendChild(placedObjectEl); @@ -919,7 +919,7 @@ function processBuildings(left, right, streetWidth, showGround, length) { // clone a bunch of fences under the parent const rotationCloneY = side === 'right' ? -90 : 90; placedObjectEl.setAttribute( - 'street-generated-fixed', + 'street-generated-clones', `model: fence; length: ${length}; spacing: 9.25; facing: ${rotationCloneY}; cycleOffset: 1` ); buildingElement.appendChild(placedObjectEl); diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js new file mode 100644 index 000000000..d32b39756 --- /dev/null +++ b/src/components/street-generated-clones.js @@ -0,0 +1,148 @@ +/* global AFRAME */ + +AFRAME.registerComponent('street-generated-clones', { + multiple: true, + schema: { + // Common properties + model: { type: 'string' }, + modelsArray: { type: 'array' }, // For random selection from multiple models + length: { type: 'number' }, // length in meters of segment + positionX: { default: 0, type: 'number' }, + positionY: { default: 0, type: 'number' }, + facing: { default: 0, type: 'number' }, // Y Rotation in degrees + randomFacing: { default: false, type: 'boolean' }, + + // Mode-specific properties + mode: { default: 'fixed', oneOf: ['fixed', 'random', 'single'] }, + + // Unified spacing and offset properties + spacing: { default: 15, type: 'number' }, // minimum distance between objects + cycleOffset: { default: 0.5, type: 'number' }, // offset as a fraction of spacing + + // Random mode properties + count: { default: 1, type: 'number' }, + + // Single mode properties + justify: { default: 'middle', oneOf: ['start', 'middle', 'end'] }, + padding: { default: 4, type: 'number' } + }, + + init: function () { + this.createdEntities = []; + }, + + update: function (oldData) { + const data = this.data; + + if (AFRAME.utils.deepEqual(oldData, data)) { + return; + } + + // Clear existing entities + this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities = []; + + // Generate new entities based on mode + switch (data.mode) { + case 'fixed': + this.generateFixed(); + break; + case 'random': + this.generateRandom(); + break; + case 'single': + this.generateSingle(); + break; + } + }, + + generateFixed: function () { + const data = this.data; + const correctedSpacing = Math.max(1, data.spacing); + const numClones = Math.floor(data.length / correctedSpacing); + + for (let i = 0; i < numClones; i++) { + const positionZ = + data.length / 2 - (i + data.cycleOffset) * correctedSpacing; + this.createClone(positionZ); + } + }, + + generateRandom: function () { + const data = this.data; + const positions = this.randPlacedElements( + data.length, + data.spacing, + data.count, + data.cycleOffset + ); + + positions.forEach((positionZ) => { + this.createClone(positionZ); + }); + }, + + generateSingle: function () { + const data = this.data; + let positionZ = 0; + + if (data.justify === 'start') { + positionZ = data.length / 2 - data.padding; + } else if (data.justify === 'end') { + positionZ = -data.length / 2 + data.padding; + } + + this.createClone(positionZ); + }, + + createClone: function (positionZ) { + const data = this.data; + const clone = document.createElement('a-entity'); + + clone.setAttribute('mixin', this.getModelMixin()); + clone.setAttribute('position', { + x: data.positionX, + y: data.positionY, + z: positionZ + }); + + const rotation = data.randomFacing ? Math.random() * 360 : data.facing; + clone.setAttribute('rotation', `0 ${rotation} 0`); + + // Add common attributes + clone.classList.add('autocreated'); + clone.setAttribute('data-no-transform', ''); + clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); + + this.el.appendChild(clone); + this.createdEntities.push(clone); + }, + + getModelMixin: function () { + const data = this.data; + if (data.modelsArray && data.modelsArray.length > 0) { + return data.modelsArray[ + Math.floor(Math.random() * data.modelsArray.length) + ]; + } + return data.model; + }, + + randPlacedElements: function (streetLength, spacing, count, offset) { + const correctedSpacing = Math.max(1, spacing); + const start = -streetLength / 2 + correctedSpacing / 2; + const end = streetLength / 2 - correctedSpacing / 2; + + // Calculate positions with offset + const len = Math.floor((end - start) / correctedSpacing) + 1; + const positions = Array(len) + .fill() + .map((_, idx) => { + // Apply the offset similar to fixed mode + return start + (idx + offset) * correctedSpacing; + }); + + // Randomly select positions + return positions.sort(() => 0.5 - Math.random()).slice(0, count); + } +}); diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js deleted file mode 100644 index 020fc9986..000000000 --- a/src/components/street-generated-fixed.js +++ /dev/null @@ -1,100 +0,0 @@ -/* global AFRAME */ - -// a-frame component to generate cloned models along a street -// this moves logic from aframe-streetmix-parsers into this component - -AFRAME.registerComponent('street-generated-fixed', { - multiple: true, - schema: { - model: { - type: 'string' - }, - length: { - // length in meters of linear path to fill with clones - type: 'number' - }, - spacing: { - // spacing in meters between clones - default: 15, - type: 'number' - }, - positionX: { - // x position of clones along the length - default: 0, - type: 'number' - }, - positionY: { - // y position of clones along the length - default: 0, - type: 'number' - }, - cycleOffset: { - // z (inbound/outbound) offset as a fraction of spacing value - default: 0.5, // this is used to place different models at different z-levels with the same spacing value - type: 'number' - }, - facing: { - default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 - type: 'number' - }, - randomFacing: { - // if true, facing is ignored and a random Y Rotation is applied to each clone - default: false, - type: 'boolean' - } - // seed: { // seed not yet supported - // default: 0, - // type: 'number' - // } - }, - init: function () { - this.createdEntities = []; - }, - update: function (oldData) { - // generate a function that creates a cloned set of x entities based on spacing and length values from the model shortname gltf file loaded in aframe - const data = this.data; - // if oldData is same as current data, then don't update - if (AFRAME.utils.deepEqual(oldData, data)) { - return; - } - - // For each clone in this.entities, remove it - this.createdEntities.forEach((entity) => { - entity.remove(); - }); - this.createdEntities = []; - - this.correctedSpacing = data.spacing < 1 ? 1 : data.spacing; // return 1 if data.spacing is less than 1 - - // Calculate number of clones needed based on length and spacing - const numClones = Math.floor(data.length / this.correctedSpacing); - - // Create clones and position them along the length - for (let i = 0; i < numClones; i++) { - const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', data.model); - // Position each clone evenly spaced along z-axis - // offset default is 0.5 so that clones don't start exactly at street start which looks weird - const positionZ = - data.length / 2 - (i + data.cycleOffset) * this.correctedSpacing; - clone.setAttribute('position', { - x: data.positionX, - y: data.positionY, - z: positionZ - }); - - if (data.randomFacing) { - clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); - } else { - clone.setAttribute('rotation', `0 ${data.facing} 0`); - } - clone.classList.add('autocreated'); - // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings - clone.setAttribute('data-no-transform', ''); - clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); - - this.el.appendChild(clone); - this.createdEntities.push(clone); - } - } -}); diff --git a/src/components/street-generated-single.js b/src/components/street-generated-single.js deleted file mode 100644 index 71cc186c0..000000000 --- a/src/components/street-generated-single.js +++ /dev/null @@ -1,90 +0,0 @@ -/* global AFRAME */ - -// a-frame component to one cloned model along a street -// this moves logic from aframe-streetmix-parsers into this component - -AFRAME.registerComponent('street-generated-single', { - multiple: true, - schema: { - model: { - type: 'string' - }, - length: { - // length in meters of segment - type: 'number' - }, - justify: { - default: 'middle', - oneOf: ['start', 'middle', 'end'] - }, - padding: { - // spacing in meters between segment edge and model - default: 4, - type: 'number' - }, - positionX: { - // x position of model - default: 0, - type: 'number' - }, - positionY: { - // y position of model - default: 0, - type: 'number' - }, - facing: { - default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 - type: 'number' - }, - randomFacing: { - // if true, facing is ignored and a random Y Rotation is applied to each clone - default: false, - type: 'boolean' - } - }, - init: function () { - this.createdEntities = []; - }, - update: function (oldData) { - const data = this.data; - // if oldData is same as current data, then don't update - if (AFRAME.utils.deepEqual(oldData, data)) { - return; - } - - // For each clone in this.entities, remove it - this.createdEntities.forEach((entity) => { - entity.remove(); - }); - this.createdEntities = []; - - const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', data.model); - - // Position z is dependent upon length and padding - let positionZ = 0; // middle - if (data.justify === 'start') { - positionZ = data.length / 2 - data.padding; - } else if (data.justify === 'end') { - positionZ = -data.length / 2 + data.padding; - } - - clone.setAttribute('position', { - x: data.positionX, - y: data.positionY, - z: positionZ - }); - - if (data.randomFacing) { - clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); - } else { - clone.setAttribute('rotation', `0 ${data.facing} 0`); - } - clone.classList.add('autocreated'); - // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings - clone.setAttribute('data-no-transform', ''); - clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); - this.el.appendChild(clone); - this.createdEntities.push(clone); - } -}); diff --git a/src/index.js b/src/index.js index 7acc06a3a..5292a44ed 100644 --- a/src/index.js +++ b/src/index.js @@ -22,13 +22,12 @@ require('./components/street-environment.js'); require('./components/intersection.js'); require('./components/obb-clipping.js'); require('./components/street-segment.js'); -require('./components/street-generated-fixed.js'); -require('./components/street-generated-single.js'); require('./components/street-generated-random.js'); require('./components/street-generated-stencil.js'); 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('./editor/index.js'); const state = useStore.getState(); From 3426494fda99339b20123557e03d6ea6ce67f041 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Mon, 2 Dec 2024 20:53:37 -0800 Subject: [PATCH 065/118] use street-generated-clones for -random --- src/aframe-streetmix-parsers.js | 52 +++++---- src/components/street-generated-clones.js | 13 ++- src/components/street-generated-random.js | 127 ---------------------- src/index.js | 1 - 4 files changed, 35 insertions(+), 158 deletions(-) delete mode 100644 src/components/street-generated-random.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 572c6dae0..f8bfa4487 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -259,10 +259,11 @@ function processSegments( ); const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-random', - `modelsArray: cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid${segments[i].type === 'scooter' ? 'ElectricScooter_1' : ''}; + 'street-generated-clones', + `mode: random; + modelsArray: cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid${segments[i].type === 'scooter' ? 'ElectricScooter_1' : ''}; length: ${length}; - placeLength: 2.03; + spacing: 2.03; facing: ${rotationCloneY}; count: ${getRandomIntInclusive(2, 5)};` ); @@ -278,8 +279,8 @@ function processSegments( segments[i].type === 'streetcar' ? 'trolley' : 'tram'; if (showVehicles) { segmentParentEl.setAttribute( - 'street-generated-random', - `model: ${objectMixinId}; length: ${length}; placeLength: 23; facing: ${rotationY}; count: 1;` + 'street-generated-clones', + `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 20; facing: ${rotationY}; count: 1;` ); } segmentParentEl.setAttribute( @@ -291,10 +292,11 @@ function processSegments( if (showVehicles && variantList[1] !== 'shared') { const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-random', - `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + 'street-generated-clones', + `mode: random; + modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; length: ${length}; - placeLength: 7.3; + spacing: 7.3; facing: ${rotationCloneY}; count: ${getRandomIntInclusive(2, 4)};` ); @@ -432,8 +434,8 @@ function processSegments( if (showVehicles) { const rotationY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-random', - `model: bus; length: ${length}; placeLength: 15; facing: ${rotationY}; count: 1;` + 'street-generated-clones', + `mode: random; model: bus; length: ${length}; spacing: 15; facing: ${rotationY}; count: 1;` ); } segmentParentEl.setAttribute( @@ -445,10 +447,11 @@ function processSegments( // const isAnimated = variantList[2] === 'animated' || globalAnimated; const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-random', - `modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + 'street-generated-clones', + `mode: random; + modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; length: ${length}; - placeLength: 7.3; + spacing: 7.3; facing: ${rotationCloneY}; count: ${getRandomIntInclusive(2, 4)};` ); @@ -457,8 +460,8 @@ function processSegments( segmentPreset = 'drive-lane'; const rotationCloneY = variantList[0] === 'left' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-random', - `model: food-trailer-rig; length: ${length}; placeLength: 7; facing: ${rotationCloneY}; count: 2;` + 'street-generated-clones', + `mode: random; model: food-trailer-rig; length: ${length}; spacing: 7; facing: ${rotationCloneY}; count: 2;` ); } else if (segments[i].type === 'flex-zone') { segmentPreset = 'parking-lane'; @@ -467,8 +470,8 @@ function processSegments( variantList[0] === 'taxi' ? 'sedan-taxi-rig' : 'sedan-rig'; const rotationCloneY = variantList[1] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( - 'street-generated-random', - `model: ${objectMixinId}; length: ${length}; placeLength: 5; facing: ${rotationCloneY}; count: 4;` + 'street-generated-clones', + `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 6; facing: ${rotationCloneY}; count: 4;` ); } segmentParentEl.setAttribute( @@ -522,15 +525,15 @@ function processSegments( } else if (segments[i].type === 'outdoor-dining') { segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; segmentParentEl.setAttribute( - 'street-generated-random', - `model: outdoor_dining; length: ${length}; placeLength: 2.27; count: 5;` + 'street-generated-clones', + `mode: random; model: outdoor_dining; length: ${length}; spacing: 3; count: 5;` ); } else if (segments[i].type === 'parklet') { segmentPreset = 'drive-lane'; const rotationCloneY = variantList[0] === 'left' ? 90 : 270; segmentParentEl.setAttribute( - 'street-generated-random', - `model: parklet; length: ${length}; placeLength: 4; count: 3; facing: ${rotationCloneY};` + 'street-generated-clones', + `mode: random; model: parklet; length: ${length}; spacing: 5.5; count: 3; facing: ${rotationCloneY};` ); } else if (segments[i].type === 'bikeshare') { const rotationCloneY = variantList[0] === 'left' ? 90 : 270; @@ -643,10 +646,11 @@ function processSegments( } } segmentParentEl.setAttribute( - 'street-generated-random', - `modelsArray: sedan-rig, self-driving-waymo-car, suv-rig; + 'street-generated-clones', + `mode: random; + modelsArray: sedan-rig, self-driving-waymo-car, suv-rig; length: ${length}; - placeLength: ${carStep}; + spacing: ${carStep}; count: ${getRandomIntInclusive(6, 8)}; facing: ${markingsRotZ - 90};` ); diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index d32b39756..643232867 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -15,9 +15,11 @@ AFRAME.registerComponent('street-generated-clones', { // Mode-specific properties mode: { default: 'fixed', oneOf: ['fixed', 'random', 'single'] }, - // Unified spacing and offset properties + // Spacing for fixed and random modes spacing: { default: 15, type: 'number' }, // minimum distance between objects - cycleOffset: { default: 0.5, type: 'number' }, // offset as a fraction of spacing + + // Fixed mode properties + cycleOffset: { default: 0.5, type: 'number' }, // offset as a fraction of spacing, only for fixed // Random mode properties count: { default: 1, type: 'number' }, @@ -73,8 +75,7 @@ AFRAME.registerComponent('street-generated-clones', { const positions = this.randPlacedElements( data.length, data.spacing, - data.count, - data.cycleOffset + data.count ); positions.forEach((positionZ) => { @@ -128,7 +129,7 @@ AFRAME.registerComponent('street-generated-clones', { return data.model; }, - randPlacedElements: function (streetLength, spacing, count, offset) { + randPlacedElements: function (streetLength, spacing, count) { const correctedSpacing = Math.max(1, spacing); const start = -streetLength / 2 + correctedSpacing / 2; const end = streetLength / 2 - correctedSpacing / 2; @@ -139,7 +140,7 @@ AFRAME.registerComponent('street-generated-clones', { .fill() .map((_, idx) => { // Apply the offset similar to fixed mode - return start + (idx + offset) * correctedSpacing; + return start + idx * correctedSpacing; }); // Randomly select positions diff --git a/src/components/street-generated-random.js b/src/components/street-generated-random.js deleted file mode 100644 index 8792ec770..000000000 --- a/src/components/street-generated-random.js +++ /dev/null @@ -1,127 +0,0 @@ -/* global AFRAME */ - -// a-frame component to generate cloned models along a street with random z position -// this moves logic from aframe-streetmix-parsers into this component -AFRAME.registerComponent('street-generated-random', { - multiple: true, - schema: { - model: { - type: 'string' - }, - modelsArray: { - type: 'array' - }, - length: { - // length in meters of linear path to fill with clones - type: 'number' - }, - count: { - // number of clones to create with random z - default: 1, - type: 'number' - }, - placeLength: { - // length of the place for each model in meters - default: 1, - type: 'number' - }, - positionX: { - // x position of clones along the length - default: 0, - type: 'number' - }, - positionY: { - // y position of clones along the length - default: 0, - type: 'number' - }, - facing: { - default: 0, // this is a Y Rotation value in degrees -- UI could offer a dropdown with options for 0, 90, 180, 270 - type: 'number' - }, - randomFacing: { - // if true, facing is ignored and a random Y Rotation is applied to each clone - default: false, - type: 'boolean' - } - // seed: { // seed not yet supported - // default: 0, - // type: 'number' - // } - }, - init: function () { - this.createdEntities = []; - }, - update: function (oldData) { - // generate a function that creates a cloned set of x entities based on spacing and length values from the model shortname gltf file loaded in aframe - const data = this.data; - // if oldData is same as current data, then don't update - if (AFRAME.utils.deepEqual(oldData, data)) { - return; - } - - // For each clone in this.entities, remove it - this.createdEntities.forEach((entity) => { - entity.remove(); - }); - this.createdEntities = []; - - // Calculate number of places needed based on length and objLength - const randPlaces = this.randPlacedElements( - data.length, - data.placeLength, - data.count - ); - - // Create clones - randPlaces.forEach((randPosZ) => { - const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', this.getRandomMixin()); - clone.setAttribute('position', { - x: data.positionX, - y: data.positionY, - z: randPosZ - }); - if (data.randomFacing) { - clone.setAttribute('rotation', `0 ${Math.random() * 360} 0`); - } else { - clone.setAttribute('rotation', `0 ${data.facing} 0`); - } - clone.classList.add('autocreated'); - // clone.setAttribute('data-ignore-raycaster', ''); // i still like clicking to zoom to individual clones, but instead this should show the generated-fixed clone settings - clone.setAttribute('data-no-transform', ''); - clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); - - this.el.appendChild(clone); - this.createdEntities.push(clone); - }); - }, - getRandomMixin: function () { - const data = this.data; - if (data.modelsArray && data.modelsArray.length > 0) { - return data.modelsArray[ - Math.floor(Math.random() * data.modelsArray.length) - ]; - } - return data.model; - }, - randPlacedElements: function (streetLength, placeLength, count) { - // Calculate start and end positions - const start = -streetLength / 2 + placeLength / 2; - const end = streetLength / 2 - placeLength / 2; - - // Calculate number of possible positions - const len = Math.floor((end - start) / placeLength) + 1; - - // Generate array of evenly spaced positions - const positions = Array(len) - .fill() - .map((_, idx) => start + idx * placeLength); - - // Randomly shuffle positions - const shuffledPositions = positions.sort(() => 0.5 - Math.random()); - - // Return only requested number of positions - return shuffledPositions.slice(0, count); - } -}); diff --git a/src/index.js b/src/index.js index 5292a44ed..019fc3061 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,6 @@ require('./components/street-environment.js'); require('./components/intersection.js'); require('./components/obb-clipping.js'); require('./components/street-segment.js'); -require('./components/street-generated-random.js'); require('./components/street-generated-stencil.js'); require('./components/street-generated-striping.js'); require('./components/street-generated-pedestrians.js'); From 555b3406847652ef3fec3edb479e62b5bdedccd7 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 3 Dec 2024 23:07:33 -0800 Subject: [PATCH 066/118] add `remove` lifecycle method --- src/components/street-generated-clones.js | 4 ++++ src/components/street-generated-pedestrians.js | 4 ++++ src/components/street-generated-rail.js | 3 +++ src/components/street-generated-striping.js | 3 +++ 4 files changed, 14 insertions(+) diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index 643232867..d5aa8f193 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -33,6 +33,10 @@ AFRAME.registerComponent('street-generated-clones', { this.createdEntities = []; }, + remove: function () { + this.createdEntities.forEach((entity) => entity.remove()); + }, + update: function (oldData) { const data = this.data; diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js index 33e952073..8bad2603e 100644 --- a/src/components/street-generated-pedestrians.js +++ b/src/components/street-generated-pedestrians.js @@ -45,6 +45,10 @@ AFRAME.registerComponent('street-generated-pedestrians', { }; }, + remove: function () { + this.createdEntities.forEach((entity) => entity.remove()); + }, + update: function (oldData) { const data = this.data; if (AFRAME.utils.deepEqual(oldData, data)) return; diff --git a/src/components/street-generated-rail.js b/src/components/street-generated-rail.js index d4b294bc8..69f18df21 100644 --- a/src/components/street-generated-rail.js +++ b/src/components/street-generated-rail.js @@ -16,6 +16,9 @@ AFRAME.registerComponent('street-generated-rail', { init: function () { this.createdEntities = []; }, + remove: function () { + this.createdEntities.forEach((entity) => entity.remove()); + }, update: function (oldData) { const data = this.data; if (AFRAME.utils.deepEqual(oldData, data)) return; diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index cdc28953d..708a40699 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -33,6 +33,9 @@ AFRAME.registerComponent('street-generated-striping', { init: function () { this.createdEntities = []; }, + remove: function () { + this.createdEntities.forEach((entity) => entity.remove()); + }, update: function (oldData) { const data = this.data; if (AFRAME.utils.deepEqual(oldData, data)) return; From 062bf487108947a7788ec5c52e9a536e91eba7af Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Tue, 3 Dec 2024 23:08:08 -0800 Subject: [PATCH 067/118] move up defining STREET global used to get street segment type presets --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 019fc3061..e3271165f 100644 --- a/src/index.js +++ b/src/index.js @@ -2,9 +2,9 @@ import 'aframe-cursor-teleport-component'; import 'aframe-extras/controls/index.js'; import useStore from './store.js'; +require('./json-utils_1.1.js'); var streetmixParsers = require('./aframe-streetmix-parsers'); var streetmixUtils = require('./tested/streetmix-utils'); -require('./json-utils_1.1.js'); var streetUtils = require('./street-utils.js'); require('./components/gltf-part'); require('./components/ocean'); From 5582881b6b1c9a1fff48f7d6c06e9d8181474c93 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 08:59:40 -0800 Subject: [PATCH 068/118] first cut of editable street segments --- src/aframe-streetmix-parsers.js | 103 ++++++++++++++++-- src/components/street-generated-stencil.js | 28 ++++- src/components/street-segment.js | 95 +++++++++++++++- .../components/components/PropertyRow.js | 6 + src/editor/components/components/Sidebar.js | 4 + .../components/StreetSegmentSidebar.js | 47 ++++++++ 6 files changed, 268 insertions(+), 15 deletions(-) create mode 100644 src/editor/components/components/StreetSegmentSidebar.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index f8bfa4487..2d7a50ba3 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -14,38 +14,121 @@ const COLORS = { const TYPES = { 'drive-lane': { + type: 'drive-lane', + color: COLORS.white, surface: 'asphalt', - color: COLORS.white + level: 0, + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: '7.3', + count: '4' + } + ] + } }, 'bus-lane': { + type: 'bus-lane', surface: 'asphalt', - color: COLORS.red + color: COLORS.red, + level: 0, + generated: { + clones: [ + { + mode: 'random', + model: 'bus', + spacing: '15', + count: '1' + } + ], + stencil: [ + { + stencils: 'word-only, word-taxi, word-bus', + spacing: '40', + padding: '10' + } + ] + } }, 'bike-lane': { + type: 'bike-lane', + color: COLORS.green, surface: 'asphalt', - color: COLORS.green + level: 0, + generated: { + stencil: [ + { + model: 'bike-arrow', + cycleOffset: '0.3', + spacing: '20' + } + ], + clones: [ + { + mode: 'random', + modelsArray: + 'cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid, ElectricScooter_1', + spacing: '2.03', + count: '4' + } + ] + } }, sidewalk: { + type: 'sidewalk', surface: 'sidewalk', - color: COLORS.white + color: COLORS.white, + level: 1, + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } }, 'parking-lane': { surface: 'concrete', - color: COLORS.lightGray + color: COLORS.lightGray, + level: 0, + 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 + } + ] + } }, divider: { surface: 'hatched', - color: COLORS.white + color: COLORS.white, + level: 0 }, grass: { surface: 'grass', - color: COLORS.white + color: COLORS.white, + level: -1 }, rail: { surface: 'asphalt', - color: COLORS.white + color: COLORS.white, + level: 0 } }; +STREET.types = TYPES; function getSeparatorMixinId(previousSegment, currentSegment) { if (previousSegment === undefined || currentSegment === undefined) { @@ -680,13 +763,15 @@ function processSegments( segmentWidthInMeters ); segmentParentEl.setAttribute('street-segment', 'length', length); - segmentParentEl.setAttribute('street-segment', 'elevation', elevation); + segmentParentEl.setAttribute('street-segment', 'level', elevation); segmentParentEl.setAttribute( + // find default color for segmentPreset 'street-segment', 'color', segmentColor ?? TYPES[segmentPreset]?.color // no error handling for segmentPreset not found ); segmentParentEl.setAttribute( + // find default surface type for segmentPreset 'street-segment', 'surface', TYPES[segmentPreset]?.surface // no error handling for segmentPreset not found diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 0a837d272..ece97c3f3 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -7,7 +7,30 @@ AFRAME.registerComponent('street-generated-stencil', { multiple: true, schema: { model: { - type: 'string' + type: 'string', + oneOf: [ + 'sharrow', + 'bike-arrow', + 'left', + 'right', + 'both', + 'all', + 'word-taxi', + 'word-only', + 'word-bus', + 'word-lane', + 'word-only-small', + 'word-yield', + 'word-slow', + 'word-xing', + 'word-stop', + 'word-loading-small', + 'perpendicular-stalls', + 'parking-t', + 'hash-left', + 'hash-right', + 'hash-chevron' + ] }, stencils: { // if present, then use this array of stencils instead of 1 model @@ -63,6 +86,9 @@ AFRAME.registerComponent('street-generated-stencil', { init: function () { this.createdEntities = []; }, + remove: function () { + this.createdEntities.forEach((entity) => entity.remove()); + }, update: function (oldData) { const data = this.data; if (AFRAME.utils.deepEqual(oldData, data)) return; diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 288d12ce1..14a02e276 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -34,13 +34,26 @@ AFRAME.registerGeometry('below-box', { AFRAME.registerComponent('street-segment', { schema: { + type: { + type: 'string', // value not used by component, used in React app instead + oneOf: [ + 'drive-lane', + 'bus-lane', + 'bike-lane', + 'sidewalk', + 'parking-lane', + 'divider', + 'grass', + 'rail' + ] + }, width: { type: 'number' }, length: { type: 'number' }, - elevation: { + level: { type: 'int', default: 0 }, @@ -68,6 +81,74 @@ AFRAME.registerComponent('street-segment', { }, init: function () { this.height = 0.2; // default height of segment surface box + this.generatedComponents = []; + this.types = window.STREET.types; // default segment types + }, + onPropertyChanged: function (property, value) { + // instead of using A-Frame component lifecycle 'update' hook, use this to reset components when type changes + if (property !== 'type') { + return; + } + console.log('onPropertyChanged', property, value); + this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import + this.remove(); + this.createGeneratedComponentsFromType(value); // add components for this type + this.updateSurfaceFromType(value); // update surface color, surface, level + this.update(); + }, + createGeneratedComponentsFromType: function (type) { + // use global preset data to create the generated components for a given segment type + const componentsToGenerate = this.types[type].generated; + + // for each of clones, stencils, rail, pedestrians, etc. + if (componentsToGenerate?.clones?.length > 0) { + componentsToGenerate.clones.forEach((clone, index) => { + if (clone?.modelsArray?.length > 0) { + this.el.setAttribute( + `street-generated-clones__${index}`, + `mode: ${clone.mode}; modelsArray: ${clone.modelsArray}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; count: ${clone.count};` + ); + } else { + this.el.setAttribute( + `street-generated-clones__${index}`, + `mode: ${clone.mode}; model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; count: ${clone.count};` + ); + } + }); + } + if (componentsToGenerate?.stencil?.length > 0) { + componentsToGenerate.stencil.forEach((clone, index) => { + if (clone?.stencils?.length > 0) { + this.el.setAttribute( + `street-generated-stencil__${index}`, + `stencils: ${clone.stencils}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; padding: ${clone.padding};` + ); + } else { + this.el.setAttribute( + `street-generated-stencil__${index}`, + `model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; count: ${clone.count};` + ); + } + }); + } + }, + updateSurfaceFromType: function (type) { + // update color, surface, level from segment type preset + this.el.setAttribute( + 'street-segment', + `surface: ${this.types[type].surface}; color: ${this.types[type].color}; level: ${this.types[type].level};` + ); // to do: this should be more elegant to check for undefined and set default values + }, + updateGeneratedComponentsList: function () { + // get all components on entity with prefix 'street-generated' + let generatedComponentList = []; + const components = this.el.components; + for (const componentName in components) { + if (componentName.startsWith('street-generated')) { + generatedComponentList.push(componentName); + } + } + this.generatedComponents = generatedComponentList; }, update: function (oldData) { const data = this.data; @@ -76,18 +157,18 @@ AFRAME.registerComponent('street-segment', { return; } this.clearMesh(); - this.height = this.calculateHeight(data.elevation); + this.height = this.calculateHeight(data.level); this.tempXPosition = this.el.getAttribute('position').x; this.el.setAttribute('position', { x: this.tempXPosition, y: this.height }); this.generateMesh(data); }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units - calculateHeight: function (elevation) { + calculateHeight: function (elevationLevel) { const stepLevel = 0.15; - if (elevation <= 0) { + if (elevationLevel <= 0) { return stepLevel; } - return stepLevel * (elevation + 1); + return stepLevel * (elevationLevel + 1); }, clearMesh: function () { // remove the geometry from the entity @@ -96,6 +177,10 @@ AFRAME.registerComponent('street-segment', { }, remove: function () { this.clearMesh(); + + this.generatedComponents.forEach((componentName) => { + this.el.removeAttribute(componentName); + }); }, generateMesh: function (data) { // create geometry diff --git a/src/editor/components/components/PropertyRow.js b/src/editor/components/components/PropertyRow.js index ca7963b30..953a8d213 100644 --- a/src/editor/components/components/PropertyRow.js +++ b/src/editor/components/components/PropertyRow.js @@ -63,6 +63,12 @@ export default class PropertyRow extends React.Component { property: !props.isSingle ? props.name : '', value: value }); + + // Call component's onPropertyChanged if it exists + const component = props.entity.components[props.componentname]; + if (component && typeof component.onPropertyChanged === 'function') { + component.onPropertyChanged(name, value); + } }, value: value }; diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index 42d797ca2..8bc9a6ecb 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -10,6 +10,7 @@ import classnames from 'classnames'; import { ArrowRightIcon, Object24Icon } from '../../icons'; import GeoSidebar from './GeoSidebar'; // Make sure to create and import this new component import IntersectionSidebar from './IntersectionSidebar'; +import StreetSegmentSidebar from './StreetSegmentSidebar'; export default class Sidebar extends React.Component { static propTypes = { entity: PropTypes.object, @@ -115,6 +116,9 @@ export default class Sidebar extends React.Component { {entity.getAttribute('intersection') && ( )} + {entity.getAttribute('street-segment') && ( + + )} ) : ( diff --git a/src/editor/components/components/StreetSegmentSidebar.js b/src/editor/components/components/StreetSegmentSidebar.js new file mode 100644 index 000000000..e231bce5a --- /dev/null +++ b/src/editor/components/components/StreetSegmentSidebar.js @@ -0,0 +1,47 @@ +import PropTypes from 'prop-types'; +import PropertyRow from './PropertyRow'; + +const StreetSegmentSidebar = ({ entity }) => { + const componentName = 'street-segment'; + // Check if entity and its components exist + const component = entity?.components?.[componentName]; + + return ( +
+
+
+ {component && component.schema && component.data && ( + <> + + + + )} +
+
+
+ ); +}; + +StreetSegmentSidebar.propTypes = { + entity: PropTypes.object.isRequired +}; + +export default StreetSegmentSidebar; From d70746568e071510f5b1045216cdcb15eb12b6e2 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 09:47:15 -0800 Subject: [PATCH 069/118] add direction --- src/aframe-streetmix-parsers.js | 3 +- .../street-generated-pedestrians.js | 6 ++-- src/components/street-segment.js | 31 +++++++++++++------ .../components/StreetSegmentSidebar.js | 10 ++++++ 4 files changed, 37 insertions(+), 13 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 2d7a50ba3..e9ac143fa 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -82,6 +82,7 @@ const TYPES = { surface: 'sidewalk', color: COLORS.white, level: 1, + direction: 'none', generated: { pedestrians: [ { @@ -564,7 +565,7 @@ function processSegments( } else if (segments[i].type === 'sidewalk' && variantList[0] !== 'empty') { segmentParentEl.setAttribute( 'street-generated-pedestrians', - `segmentWidth: ${segmentWidthInMeters}; density: ${variantList[0]}; length: ${length}; direction: random;` + `segmentWidth: ${segmentWidthInMeters}; density: ${variantList[0]}; length: ${length};` ); } else if (segments[i].type === 'sidewalk-wayfinding') { segmentParentEl.setAttribute( diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js index 8bad2603e..6c73f6ceb 100644 --- a/src/components/street-generated-pedestrians.js +++ b/src/components/street-generated-pedestrians.js @@ -20,8 +20,8 @@ AFRAME.registerComponent('street-generated-pedestrians', { }, direction: { type: 'string', - default: 'random', - oneOf: ['random', 'inbound', 'outbound'] + default: 'none', + oneOf: ['none', 'inbound', 'outbound'] }, // animated: { // // load 8 animated characters instead of 16 static characters @@ -96,7 +96,7 @@ AFRAME.registerComponent('street-generated-pedestrians', { pedestrian.setAttribute('mixin', `${variantPrefix}${variantNumber}`); // Set rotation based on direction - if (data.direction === 'random' && Math.random() < 0.5) { + if (data.direction === 'none' && Math.random() < 0.5) { pedestrian.setAttribute('rotation', '0 180 0'); } else if (data.direction === 'outbound') { pedestrian.setAttribute('rotation', '0 180 0'); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 14a02e276..f582007da 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -59,7 +59,7 @@ AFRAME.registerComponent('street-segment', { }, direction: { type: 'string', - oneOf: ['inbound', 'outbound'] + oneOf: ['none', 'inbound', 'outbound'] }, surface: { type: 'string', @@ -86,19 +86,24 @@ AFRAME.registerComponent('street-segment', { }, onPropertyChanged: function (property, value) { // instead of using A-Frame component lifecycle 'update' hook, use this to reset components when type changes - if (property !== 'type') { + const updateProperties = ['type', 'direction']; + if (!updateProperties.includes(property)) { return; } console.log('onPropertyChanged', property, value); + let typeObject = this.types[value]; + if (property === 'direction') { + typeObject = this.types[this.data.type]; + } this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import this.remove(); - this.createGeneratedComponentsFromType(value); // add components for this type - this.updateSurfaceFromType(value); // update surface color, surface, level - this.update(); + this.createGeneratedComponentsFromType(typeObject); // add components for this type + this.updateSurfaceFromType(typeObject); // update surface color, surface, level + this.update(); // force update to surface, needed for when the type value didn't change but still need to update the generated components }, - createGeneratedComponentsFromType: function (type) { + createGeneratedComponentsFromType: function (typeObject) { // use global preset data to create the generated components for a given segment type - const componentsToGenerate = this.types[type].generated; + const componentsToGenerate = typeObject.generated; // for each of clones, stencils, rail, pedestrians, etc. if (componentsToGenerate?.clones?.length > 0) { @@ -131,12 +136,20 @@ AFRAME.registerComponent('street-segment', { } }); } + if (componentsToGenerate?.pedestrians?.length > 0) { + componentsToGenerate.pedestrians.forEach((pedestrian, index) => { + this.el.setAttribute( + `street-generated-pedestrians__${index}`, + `segmentWidth: ${this.data.width}; density: ${pedestrian.density}; length: ${this.data.length}; direction: ${this.data.direction};` + ); + }); + } }, - updateSurfaceFromType: function (type) { + updateSurfaceFromType: function (typeObject) { // update color, surface, level from segment type preset this.el.setAttribute( 'street-segment', - `surface: ${this.types[type].surface}; color: ${this.types[type].color}; level: ${this.types[type].level};` + `surface: ${typeObject.surface}; color: ${typeObject.color}; level: ${typeObject.level};` ); // to do: this should be more elegant to check for undefined and set default values }, updateGeneratedComponentsList: function () { diff --git a/src/editor/components/components/StreetSegmentSidebar.js b/src/editor/components/components/StreetSegmentSidebar.js index e231bce5a..7433e21c7 100644 --- a/src/editor/components/components/StreetSegmentSidebar.js +++ b/src/editor/components/components/StreetSegmentSidebar.js @@ -32,6 +32,16 @@ const StreetSegmentSidebar = ({ entity }) => { isSingle={false} entity={entity} /> + )} From 3dbae43766c873bb0abc77237d78a4b8ec270f38 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 10:44:11 -0800 Subject: [PATCH 070/118] inbound / outbound v1 --- src/components/street-generated-clones.js | 16 +++++++++++++--- src/components/street-segment.js | 4 ++-- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index d5aa8f193..bce056206 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -11,6 +11,7 @@ AFRAME.registerComponent('street-generated-clones', { positionY: { default: 0, type: 'number' }, facing: { default: 0, type: 'number' }, // Y Rotation in degrees randomFacing: { default: false, type: 'boolean' }, + direction: { type: 'string', oneOf: ['none', 'inbound', 'outbound'] }, // not used if facing defined? // Mode-specific properties mode: { default: 'fixed', oneOf: ['fixed', 'random', 'single'] }, @@ -102,22 +103,31 @@ AFRAME.registerComponent('street-generated-clones', { createClone: function (positionZ) { const data = this.data; + const mixinId = this.getModelMixin(); const clone = document.createElement('a-entity'); - clone.setAttribute('mixin', this.getModelMixin()); + clone.setAttribute('mixin', mixinId); clone.setAttribute('position', { x: data.positionX, y: data.positionY, z: positionZ }); - const rotation = data.randomFacing ? Math.random() * 360 : data.facing; + let rotation = data.randomFacing ? Math.random() * 360 : data.facing; + console.log('data.direction', data.direction); + if (data.direction === 'outbound') { + rotation = 180; + } + if (data.direction === 'inbound') { + rotation = 0; + } + console.log('rotation', rotation); clone.setAttribute('rotation', `0 ${rotation} 0`); // Add common attributes clone.classList.add('autocreated'); clone.setAttribute('data-no-transform', ''); - clone.setAttribute('data-layer-name', 'Cloned Model • ' + data.model); + clone.setAttribute('data-layer-name', 'Cloned Model • ' + mixinId); this.el.appendChild(clone); this.createdEntities.push(clone); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index f582007da..10ebed398 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -111,12 +111,12 @@ AFRAME.registerComponent('street-segment', { if (clone?.modelsArray?.length > 0) { this.el.setAttribute( `street-generated-clones__${index}`, - `mode: ${clone.mode}; modelsArray: ${clone.modelsArray}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; count: ${clone.count};` + `mode: ${clone.mode}; modelsArray: ${clone.modelsArray}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; count: ${clone.count};` ); } else { this.el.setAttribute( `street-generated-clones__${index}`, - `mode: ${clone.mode}; model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; count: ${clone.count};` + `mode: ${clone.mode}; model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; count: ${clone.count};` ); } }); From 7134df461ef498a89ac4c38e96584fa069239b62 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 13:13:27 -0800 Subject: [PATCH 071/118] revert inspector hack --- src/components/street-segment.js | 29 ++++++++----------- .../components/components/PropertyRow.js | 6 ---- 2 files changed, 12 insertions(+), 23 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 10ebed398..2a6fab893 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -84,23 +84,6 @@ AFRAME.registerComponent('street-segment', { this.generatedComponents = []; this.types = window.STREET.types; // default segment types }, - onPropertyChanged: function (property, value) { - // instead of using A-Frame component lifecycle 'update' hook, use this to reset components when type changes - const updateProperties = ['type', 'direction']; - if (!updateProperties.includes(property)) { - return; - } - console.log('onPropertyChanged', property, value); - let typeObject = this.types[value]; - if (property === 'direction') { - typeObject = this.types[this.data.type]; - } - this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import - this.remove(); - this.createGeneratedComponentsFromType(typeObject); // add components for this type - this.updateSurfaceFromType(typeObject); // update surface color, surface, level - this.update(); // force update to surface, needed for when the type value didn't change but still need to update the generated components - }, createGeneratedComponentsFromType: function (typeObject) { // use global preset data to create the generated components for a given segment type const componentsToGenerate = typeObject.generated; @@ -165,10 +148,22 @@ AFRAME.registerComponent('street-segment', { }, update: function (oldData) { const data = this.data; + const dataDiff = AFRAME.utils.diff(oldData, data); // if oldData is same as current data, then don't update if (AFRAME.utils.deepEqual(oldData, data)) { return; } + // regenerate components if only type has changed + if ( + Object.keys(dataDiff).length === 1 && + Object.keys(dataDiff).includes('type') + ) { + let typeObject = this.types[this.data.type]; + this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import + this.remove(); + this.createGeneratedComponentsFromType(typeObject); // add components for this type + this.updateSurfaceFromType(typeObject); // update surface color, surface, level + } this.clearMesh(); this.height = this.calculateHeight(data.level); this.tempXPosition = this.el.getAttribute('position').x; diff --git a/src/editor/components/components/PropertyRow.js b/src/editor/components/components/PropertyRow.js index 953a8d213..ca7963b30 100644 --- a/src/editor/components/components/PropertyRow.js +++ b/src/editor/components/components/PropertyRow.js @@ -63,12 +63,6 @@ export default class PropertyRow extends React.Component { property: !props.isSingle ? props.name : '', value: value }); - - // Call component's onPropertyChanged if it exists - const component = props.entity.components[props.componentname]; - if (component && typeof component.onPropertyChanged === 'function') { - component.onPropertyChanged(name, value); - } }, value: value }; From 92d147375858564f3014135b57939923dfb72b6a Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 13:28:50 -0800 Subject: [PATCH 072/118] move type definitions to street-segment --- src/aframe-streetmix-parsers.js | 170 ++---------------------------- src/components/street-segment.js | 172 +++++++++++++++++++++++++++---- 2 files changed, 158 insertions(+), 184 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index e9ac143fa..842626c6a 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -2,135 +2,6 @@ var streetmixParsersTested = require('./tested/aframe-streetmix-parsers-tested'); var { segmentVariants } = require('./segments-variants.js'); -const COLORS = { - red: '#ff9393', - blue: '#00b6b6', - green: '#adff83', - yellow: '#f7d117', - lightGray: '#dddddd', - white: '#ffffff', - brown: '#664B00' -}; - -const TYPES = { - 'drive-lane': { - type: 'drive-lane', - color: COLORS.white, - surface: 'asphalt', - level: 0, - generated: { - clones: [ - { - mode: 'random', - modelsArray: - 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', - spacing: '7.3', - count: '4' - } - ] - } - }, - 'bus-lane': { - type: 'bus-lane', - surface: 'asphalt', - color: COLORS.red, - level: 0, - generated: { - clones: [ - { - mode: 'random', - model: 'bus', - spacing: '15', - count: '1' - } - ], - stencil: [ - { - stencils: 'word-only, word-taxi, word-bus', - spacing: '40', - padding: '10' - } - ] - } - }, - 'bike-lane': { - type: 'bike-lane', - color: COLORS.green, - surface: 'asphalt', - level: 0, - generated: { - stencil: [ - { - model: 'bike-arrow', - cycleOffset: '0.3', - spacing: '20' - } - ], - clones: [ - { - mode: 'random', - modelsArray: - 'cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid, ElectricScooter_1', - spacing: '2.03', - count: '4' - } - ] - } - }, - sidewalk: { - type: 'sidewalk', - surface: 'sidewalk', - color: COLORS.white, - level: 1, - direction: 'none', - generated: { - pedestrians: [ - { - density: 'normal' - } - ] - } - }, - 'parking-lane': { - surface: 'concrete', - color: COLORS.lightGray, - level: 0, - 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 - } - ] - } - }, - divider: { - surface: 'hatched', - color: COLORS.white, - level: 0 - }, - grass: { - surface: 'grass', - color: COLORS.white, - level: -1 - }, - rail: { - surface: 'asphalt', - color: COLORS.white, - level: 0 - } -}; -STREET.types = TYPES; - function getSeparatorMixinId(previousSegment, currentSegment) { if (previousSegment === undefined || currentSegment === undefined) { return null; @@ -230,15 +101,15 @@ function getRandomIntInclusive(min, max) { function getSegmentColor(variant) { if ((variant === 'red') | (variant === 'colored')) { - return COLORS.red; + return window.STREET.colors.red; } if (variant === 'blue') { - return COLORS.blue; + return window.STREET.colors.blue; } if ((variant === 'green') | (variant === 'grass')) { - return COLORS.green; + return window.STREET.colors.green; } - return COLORS.white; + return window.STREET.colors.white; } // offset to center the street around global x position of 0 @@ -290,9 +161,6 @@ function processSegments( streetParentEl.setAttribute('data-layer-name', 'Street Segments Container'); streetParentEl.setAttribute('data-no-transform', ''); - // experimental - create a new array children for the new data structure - let newManagedStreetDataStructureChildren = []; - var cumulativeWidthInMeters = 0; for (var i = 0; i < segments.length; i++) { var segmentColor = null; @@ -769,13 +637,13 @@ function processSegments( // find default color for segmentPreset 'street-segment', 'color', - segmentColor ?? TYPES[segmentPreset]?.color // no error handling for segmentPreset not found + segmentColor ?? window.STREET.types[segmentPreset]?.color // no error handling for segmentPreset not found ); segmentParentEl.setAttribute( // find default surface type for segmentPreset 'street-segment', 'surface', - TYPES[segmentPreset]?.surface // no error handling for segmentPreset not found + window.STREET.types[segmentPreset]?.surface // no error handling for segmentPreset not found ); let currentSegment = segments[i]; @@ -801,16 +669,6 @@ function processSegments( } } - // experimental - output the new data structure - let childData = { - id: segments[i].id, // this will collide with other segment ID if there are multiple streets placed with identical segment id's - type: segments[i].type, - width: segmentWidthInMeters, - elevation: elevation - }; - newManagedStreetDataStructureChildren.push(childData); - - // returns JSON output instead // append the new surfaceElement to the segmentParentEl streetParentEl.append(segmentParentEl); segmentParentEl.setAttribute('position', segmentPositionX + ' 0 0'); @@ -819,20 +677,6 @@ function processSegments( 'Segment • ' + segments[i].type + ', ' + variantList[0] ); } - // experimental, output the new data structure - let newManagedStreetDataStructureInstance = { - // name: "string", // streetmix name not accessible from this function - type: 'managed_street', - width: cumulativeWidthInMeters, // this is the user-specified RoW width, not cumulative width of segments - // length: float, // not accessible from this function - // transform: { - // position: { x: float, y: float, z: float} - // rotation: { x: float, y: float, z: float} - // scale: { x: float, y: float, z: float} - // }, - children: newManagedStreetDataStructureChildren - }; - console.log(newManagedStreetDataStructureInstance); // create new brown box to represent ground underneath street const dirtBox = document.createElement('a-box'); @@ -841,7 +685,7 @@ function processSegments( dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 dirtBox.setAttribute('width', cumulativeWidthInMeters); dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side - dirtBox.setAttribute('material', `color: ${COLORS.brown};`); + dirtBox.setAttribute('material', `color: ${STREET.colors.brown};`); dirtBox.setAttribute('data-layer-name', 'Underground'); streetParentEl.append(dirtBox); return streetParentEl; diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 2a6fab893..fe7c69fe2 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -9,28 +9,135 @@
*/ -AFRAME.registerGeometry('below-box', { - schema: { - depth: { default: 1, min: 0 }, - height: { default: 1, min: 0 }, - width: { default: 1, min: 0 }, - segmentsHeight: { default: 1, min: 1, max: 20, type: 'int' }, - segmentsWidth: { default: 1, min: 1, max: 20, type: 'int' }, - segmentsDepth: { default: 1, min: 1, max: 20, type: 'int' } - }, +const COLORS = { + red: '#ff9393', + blue: '#00b6b6', + green: '#adff83', + yellow: '#f7d117', + lightGray: '#dddddd', + white: '#ffffff', + brown: '#664B00' +}; +STREET.colors = COLORS; - init: function (data) { - this.geometry = new THREE.BoxGeometry( - data.width, - data.height, - data.depth, - data.segmentsWidth, - data.segmentsHeight, - data.segmentsDepth - ); - this.geometry.translate(0, -data.height / 2, 0); +const TYPES = { + 'drive-lane': { + type: 'drive-lane', + color: COLORS.white, + surface: 'asphalt', + level: 0, + generated: { + clones: [ + { + mode: 'random', + modelsArray: + 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', + spacing: '7.3', + count: '4' + } + ] + } + }, + 'bus-lane': { + type: 'bus-lane', + surface: 'asphalt', + color: COLORS.red, + level: 0, + generated: { + clones: [ + { + mode: 'random', + model: 'bus', + spacing: '15', + count: '1' + } + ], + stencil: [ + { + stencils: 'word-only, word-taxi, word-bus', + spacing: '40', + padding: '10' + } + ] + } + }, + 'bike-lane': { + type: 'bike-lane', + color: COLORS.green, + surface: 'asphalt', + level: 0, + generated: { + stencil: [ + { + model: 'bike-arrow', + cycleOffset: '0.3', + spacing: '20' + } + ], + clones: [ + { + mode: 'random', + modelsArray: + 'cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid, ElectricScooter_1', + spacing: '2.03', + count: '4' + } + ] + } + }, + sidewalk: { + type: 'sidewalk', + surface: 'sidewalk', + color: COLORS.white, + level: 1, + direction: 'none', + generated: { + pedestrians: [ + { + density: 'normal' + } + ] + } + }, + 'parking-lane': { + surface: 'concrete', + color: COLORS.lightGray, + level: 0, + 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 + } + ] + } + }, + divider: { + surface: 'hatched', + color: COLORS.white, + level: 0 + }, + grass: { + surface: 'grass', + color: COLORS.white, + level: -1 + }, + rail: { + surface: 'asphalt', + color: COLORS.white, + level: 0 } -}); +}; +STREET.types = TYPES; AFRAME.registerComponent('street-segment', { schema: { @@ -82,7 +189,7 @@ AFRAME.registerComponent('street-segment', { init: function () { this.height = 0.2; // default height of segment surface box this.generatedComponents = []; - this.types = window.STREET.types; // default segment types + this.types = TYPES; // default segment types }, createGeneratedComponentsFromType: function (typeObject) { // use global preset data to create the generated components for a given segment type @@ -268,3 +375,26 @@ AFRAME.registerComponent('street-segment', { return [repeatX, repeatY, offsetX]; } }); + +AFRAME.registerGeometry('below-box', { + schema: { + depth: { default: 1, min: 0 }, + height: { default: 1, min: 0 }, + width: { default: 1, min: 0 }, + segmentsHeight: { default: 1, min: 1, max: 20, type: 'int' }, + segmentsWidth: { default: 1, min: 1, max: 20, type: 'int' }, + segmentsDepth: { default: 1, min: 1, max: 20, type: 'int' } + }, + + init: function (data) { + this.geometry = new THREE.BoxGeometry( + data.width, + data.height, + data.depth, + data.segmentsWidth, + data.segmentsHeight, + data.segmentsDepth + ); + this.geometry.translate(0, -data.height / 2, 0); + } +}); From b64141ebd71b63aa0ac180342e676ba429a3d9c3 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 13:47:02 -0800 Subject: [PATCH 073/118] direction for clones and stencils --- src/components/street-generated-stencil.js | 19 +++++++++++++++---- src/components/street-segment.js | 10 ++++++++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index ece97c3f3..6536c2cbc 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -77,6 +77,11 @@ AFRAME.registerComponent('street-generated-stencil', { stencilHeight: { default: 0, type: 'number' + }, + direction: { + // specifying inbound/outbound directions will overwrite facing/randomFacing + type: 'string', + oneOf: ['none', 'inbound', 'outbound'] } // seed: { // seed not yet supported // default: 0, @@ -100,8 +105,8 @@ AFRAME.registerComponent('street-generated-stencil', { // Use either stencils array or single model let stencilsToUse = data.stencils.length > 0 ? data.stencils : [data.model]; - // Reverse stencil order if facing is 180 degrees - if (data.facing === 180) { + // Reverse stencil order if inbound + if (data.direction === 'inbound') { stencilsToUse = stencilsToUse.slice().reverse(); } @@ -141,10 +146,16 @@ AFRAME.registerComponent('street-generated-stencil', { }); } - // Set rotation - either random or specified facing - const rotation = data.randomFacing + // Set rotation - either random, specified facing, or inbound/outbound + let rotation = data.randomFacing ? `-90 ${Math.random() * 360} 0` : `-90 ${data.facing} 0`; + if (data.direction === 'inbound') { + rotation = `-90 180 0`; + } + if (data.direction === 'outbound') { + rotation = `-90 0 0`; + } clone.setAttribute('rotation', rotation); // Add metadata diff --git a/src/components/street-segment.js b/src/components/street-segment.js index fe7c69fe2..31d9a503d 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -271,6 +271,16 @@ AFRAME.registerComponent('street-segment', { this.createGeneratedComponentsFromType(typeObject); // add components for this type this.updateSurfaceFromType(typeObject); // update surface color, surface, level } + // propagate change of direction to generated components is solo changed + if ( + Object.keys(dataDiff).length === 1 && + Object.keys(dataDiff).includes('direction') + ) { + this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import + for (const componentName of this.generatedComponents) { + this.el.setAttribute(componentName, 'direction', this.data.direction); + } + } this.clearMesh(); this.height = this.calculateHeight(data.level); this.tempXPosition = this.el.getAttribute('position').x; From 5941e1a381b6244e1b5edf65566f860fc2e07c0b Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 14:13:33 -0800 Subject: [PATCH 074/118] support directions for stencils and clones --- src/aframe-streetmix-parsers.js | 41 ++++++++++------------ src/components/street-generated-stencil.js | 13 +++---- src/components/street-segment.js | 4 +-- 3 files changed, 27 insertions(+), 31 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 842626c6a..9fc420055 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -184,10 +184,10 @@ function processSegments( // elevation property from streetmix segment const elevation = segments[i].elevation; - // Note: segment 3d models are outbound by default - // If segment variant inbound, rotate segment model by 180 degrees - var rotationY = - variantList[0] === 'inbound' || variantList[1] === 'inbound' ? 180 : 0; + var direction = + variantList[0] === 'inbound' || variantList[1] === 'inbound' + ? 'inbound' + : 'outbound'; // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that var segmentPreset = segments[i].type; @@ -196,7 +196,7 @@ function processSegments( if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { segmentParentEl.setAttribute( 'street-generated-stencil', - `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15; facing: ${rotationY}` + `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15; direction: ${direction}` ); } else if ( segments[i].type === 'bike-lane' || @@ -207,16 +207,15 @@ function processSegments( segmentColor = getSegmentColor(variantList[1]); segmentParentEl.setAttribute( 'street-generated-stencil', - `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20; facing: ${rotationY};` + `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20; direction: ${direction};` ); - const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', `mode: random; modelsArray: cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid${segments[i].type === 'scooter' ? 'ElectricScooter_1' : ''}; length: ${length}; spacing: 2.03; - facing: ${rotationCloneY}; + direction: ${direction}; count: ${getRandomIntInclusive(2, 5)};` ); } else if ( @@ -232,7 +231,7 @@ function processSegments( if (showVehicles) { segmentParentEl.setAttribute( 'street-generated-clones', - `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 20; facing: ${rotationY}; count: 1;` + `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 20; direction: ${direction}; count: 1;` ); } segmentParentEl.setAttribute( @@ -242,14 +241,13 @@ function processSegments( } else if (segments[i].type === 'turn-lane') { segmentPreset = 'drive-lane'; // use normal drive lane road material if (showVehicles && variantList[1] !== 'shared') { - const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', `mode: random; modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; length: ${length}; spacing: 7.3; - facing: ${rotationCloneY}; + direction: ${direction}; count: ${getRandomIntInclusive(2, 4)};` ); } @@ -268,12 +266,12 @@ function processSegments( } segmentParentEl.setAttribute( 'street-generated-stencil', - `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.4; spacing: 20; facing: ${rotationY};` + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.4; spacing: 20; direction: ${direction};` ); if (variantList[1] === 'shared') { segmentParentEl.setAttribute( 'street-generated-stencil__2', - `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.6; spacing: 20; facing: ${rotationY + 180};` + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.6; spacing: 20; direction: ${direction}; facing: 180;` ); } } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { @@ -384,51 +382,47 @@ function processSegments( segmentColor = getSegmentColor(variantList[1]); if (showVehicles) { - const rotationY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', - `mode: random; model: bus; length: ${length}; spacing: 15; facing: ${rotationY}; count: 1;` + `mode: random; model: bus; length: ${length}; spacing: 15; direction: ${direction}; count: 1;` ); } segmentParentEl.setAttribute( 'street-generated-stencil', - `stencils: word-only, word-taxi, word-bus; length: ${length}; spacing: 40; padding: 10; facing: ${rotationY}` + `stencils: word-only, word-taxi, word-bus; length: ${length}; spacing: 40; padding: 10; direction: ${direction}` ); } else if (segments[i].type === 'drive-lane') { if (showVehicles) { // const isAnimated = variantList[2] === 'animated' || globalAnimated; - const rotationCloneY = variantList[0] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', `mode: random; modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; length: ${length}; spacing: 7.3; - facing: ${rotationCloneY}; + direction: ${direction}; count: ${getRandomIntInclusive(2, 4)};` ); } } else if (segments[i].type === 'food-truck') { segmentPreset = 'drive-lane'; - const rotationCloneY = variantList[0] === 'left' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', - `mode: random; model: food-trailer-rig; length: ${length}; spacing: 7; facing: ${rotationCloneY}; count: 2;` + `mode: random; model: food-trailer-rig; length: ${length}; spacing: 7; direction: ${direction}; count: 2;` ); } else if (segments[i].type === 'flex-zone') { segmentPreset = 'parking-lane'; if (showVehicles) { const objectMixinId = variantList[0] === 'taxi' ? 'sedan-taxi-rig' : 'sedan-rig'; - const rotationCloneY = variantList[1] === 'inbound' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', - `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 6; facing: ${rotationCloneY}; count: 4;` + `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 6; direction: ${direction}; count: 4;` ); } segmentParentEl.setAttribute( 'street-generated-stencil', - `stencils: word-loading-small, word-only-small; length: ${length}; spacing: 40; padding: 10; facing: ${rotationY}` + `stencils: word-loading-small, word-only-small; length: ${length}; spacing: 40; padding: 10; direction: ${direction}` ); } else if (segments[i].type === 'sidewalk' && variantList[0] !== 'empty') { segmentParentEl.setAttribute( @@ -633,6 +627,7 @@ function processSegments( ); segmentParentEl.setAttribute('street-segment', 'length', length); segmentParentEl.setAttribute('street-segment', 'level', elevation); + segmentParentEl.setAttribute('street-segment', 'direction', direction); segmentParentEl.setAttribute( // find default color for segmentPreset 'street-segment', diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 6536c2cbc..4834ab514 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -147,16 +147,17 @@ AFRAME.registerComponent('street-generated-stencil', { } // Set rotation - either random, specified facing, or inbound/outbound - let rotation = data.randomFacing - ? `-90 ${Math.random() * 360} 0` - : `-90 ${data.facing} 0`; + let rotationY = 0; if (data.direction === 'inbound') { - rotation = `-90 180 0`; + rotationY = 180 + data.facing; } if (data.direction === 'outbound') { - rotation = `-90 0 0`; + rotationY = 0 + data.facing; } - clone.setAttribute('rotation', rotation); + if (data.randomFacing) { + rotationY = Math.random() * 360; + } + clone.setAttribute('rotation', `-90 ${rotationY} 0`); // Add metadata clone.classList.add('autocreated'); diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 31d9a503d..f7a71bd95 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -216,12 +216,12 @@ AFRAME.registerComponent('street-segment', { if (clone?.stencils?.length > 0) { this.el.setAttribute( `street-generated-stencil__${index}`, - `stencils: ${clone.stencils}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; padding: ${clone.padding};` + `stencils: ${clone.stencils}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; padding: ${clone.padding};` ); } else { this.el.setAttribute( `street-generated-stencil__${index}`, - `model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; facing: 0; count: ${clone.count};` + `model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; count: ${clone.count};` ); } }); From 30335048e4bd86e734cfdab170eeaa46aa279cfb Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 14:57:12 -0800 Subject: [PATCH 075/118] add horizontal scrolling --- src/editor/lib/EditorControls.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/editor/lib/EditorControls.js b/src/editor/lib/EditorControls.js index cf899f2de..4b92530d9 100644 --- a/src/editor/lib/EditorControls.js +++ b/src/editor/lib/EditorControls.js @@ -291,8 +291,16 @@ THREE.EditorControls = function (_object, domElement) { function onMouseWheel(event) { event.preventDefault(); - // Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460 - scope.zoom(delta.set(0, 0, event.deltaY > 0 ? 1 : -1)); + if (Math.abs(event.deltaY) > Math.abs(event.deltaX)) { + // Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460 + scope.zoom(delta.set(0, 0, event.deltaY > 0 ? 1 : -1)); + } else { + if (event.deltaX !== 0) { + // Pan the camera horizontally based on deltaX + // We use a smaller multiplier for horizontal scroll to make it less sensitive + scope.pan(delta.set(event.deltaX > 0 ? 10 : -10, 0, 0)); + } + } } function contextmenu(event) { From eb212947c93b0d00b3e72cb08d39d82811c66a8f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 15:29:08 -0800 Subject: [PATCH 076/118] fix parking stripe orientation --- src/components/street-generated-stencil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 4834ab514..bae350ace 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -147,7 +147,7 @@ AFRAME.registerComponent('street-generated-stencil', { } // Set rotation - either random, specified facing, or inbound/outbound - let rotationY = 0; + let rotationY = data.facing; if (data.direction === 'inbound') { rotationY = 180 + data.facing; } From a99f353b5cae5cae67de045377822539f1dc65aa Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 15:47:17 -0800 Subject: [PATCH 077/118] allow direction and facing together --- src/components/street-generated-clones.js | 15 ++++++++------- src/components/street-generated-stencil.js | 7 ++++--- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index bce056206..ad232d11d 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -113,16 +113,17 @@ AFRAME.registerComponent('street-generated-clones', { z: positionZ }); - let rotation = data.randomFacing ? Math.random() * 360 : data.facing; - console.log('data.direction', data.direction); + var rotationY = data.facing; + if (data.direction === 'inbound') { + rotationY = 0 + data.facing; + } if (data.direction === 'outbound') { - rotation = 180; + rotationY = 180 - data.facing; } - if (data.direction === 'inbound') { - rotation = 0; + if (data.randomFacing) { + rotationY = Math.random() * 360; } - console.log('rotation', rotation); - clone.setAttribute('rotation', `0 ${rotation} 0`); + clone.setAttribute('rotation', `0 ${rotationY} 0`); // Add common attributes clone.classList.add('autocreated'); diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index bae350ace..0c7a0ee9e 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -29,7 +29,8 @@ AFRAME.registerComponent('street-generated-stencil', { 'parking-t', 'hash-left', 'hash-right', - 'hash-chevron' + 'hash-chevron', + 'solid-stripe' ] }, stencils: { @@ -147,12 +148,12 @@ AFRAME.registerComponent('street-generated-stencil', { } // Set rotation - either random, specified facing, or inbound/outbound - let rotationY = data.facing; + var rotationY = data.facing; if (data.direction === 'inbound') { rotationY = 180 + data.facing; } if (data.direction === 'outbound') { - rotationY = 0 + data.facing; + rotationY = 0 - data.facing; } if (data.randomFacing) { rotationY = Math.random() * 360; From 24614c5391c8c020cd956a7d3e47e4b0c96ba07b Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 22:08:29 -0800 Subject: [PATCH 078/118] add more stencil types --- src/components/street-generated-stencil.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 0c7a0ee9e..5eac38c1b 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -13,6 +13,9 @@ AFRAME.registerComponent('street-generated-stencil', { 'bike-arrow', 'left', 'right', + 'straight', + 'left-straight', + 'right-straight', 'both', 'all', 'word-taxi', From a2213f7e0463f663a2a3c79c6db29de7cb3a3f6b Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 4 Dec 2024 23:28:23 -0800 Subject: [PATCH 079/118] improve street-segment side panel --- src/aframe-streetmix-parsers.js | 2 +- src/editor/components/components/Sidebar.js | 171 +++++++++++--------- src/editor/icons/icons.jsx | 46 +++++- src/editor/icons/index.js | 3 +- 4 files changed, 144 insertions(+), 78 deletions(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 9fc420055..8f4892cf2 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -669,7 +669,7 @@ function processSegments( segmentParentEl.setAttribute('position', segmentPositionX + ' 0 0'); segmentParentEl.setAttribute( 'data-layer-name', - 'Segment • ' + segments[i].type + ', ' + variantList[0] + '' + segments[i].type + ' • ' + variantList[0] ); } diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index 8bc9a6ecb..a389b6740 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -7,10 +7,12 @@ import PropTypes from 'prop-types'; import React from 'react'; import capitalize from 'lodash-es/capitalize'; import classnames from 'classnames'; -import { ArrowRightIcon, Object24Icon } from '../../icons'; +import { ArrowRightIcon, Object24Icon, Segment34Icon } from '../../icons'; import GeoSidebar from './GeoSidebar'; // Make sure to create and import this new component import IntersectionSidebar from './IntersectionSidebar'; import StreetSegmentSidebar from './StreetSegmentSidebar'; +import AdvancedComponents from './AdvancedComponents'; + export default class Sidebar extends React.Component { static propTypes = { entity: PropTypes.object, @@ -64,89 +66,108 @@ export default class Sidebar extends React.Component { this.setState({ showSideBar: !this.state.showSideBar }); }; + renderSidebarContent() { + const { entity } = this.props; + + if (entity.id === 'reference-layers') { + return ; + } + + if (entity.getAttribute('street-segment')) { + return ( + <> + +
+ +
+ + ); + } + + const isIntersection = entity.getAttribute('intersection'); + const hasNoTransform = entity.hasAttribute('data-no-transform'); + + return ( + <> + {entity.mixinEls.length > 0 && } + + {!hasNoTransform && ( + + )} + + {isIntersection && } + + + ); + } + + renderCollapsedSidebar(entityName, formattedMixin) { + return ( +
+
+ + {entityName || formattedMixin} + +
+ +
+
+
+ ); + } + render() { - const entity = this.props.entity; - const visible = this.props.visible; + const { entity, visible } = this.props; + + if (!entity || !visible) { + return
; + } + + const entityName = entity.getDOMAttribute('data-layer-name'); + const entityMixin = entity.getDOMAttribute('mixin'); + const formattedMixin = entityMixin + ? capitalize(entityMixin.replaceAll('-', ' ').replaceAll('_', ' ')) + : null; + const className = classnames({ outliner: true, hide: this.state.showSideBar, 'mt-16': true }); - if (entity && visible) { - const entityName = entity.getDOMAttribute('data-layer-name'); - const entityMixin = entity.getDOMAttribute('mixin'); - const formattedMixin = entityMixin - ? capitalize(entityMixin.replaceAll('-', ' ').replaceAll('_', ' ')) - : null; - return ( -
- {this.state.showSideBar ? ( - <> -
-
- - {entityName || formattedMixin} -
-
- -
-
-
- {entity.id !== 'reference-layers' ? ( - <> - {!!entity.mixinEls.length && } - {entity.hasAttribute('data-no-transform') ? ( - <> - ) : ( - - )} - {entity.getAttribute('intersection') && ( - - )} - {entity.getAttribute('street-segment') && ( - - )} - - + + return ( +
+ {this.state.showSideBar ? ( + <> +
+
+ {entity.getAttribute('street-segment') ? ( + ) : ( - + )} + {entityName || formattedMixin}
- - ) : ( - <> -
-
- - {entityName || formattedMixin} - -
- -
-
+
+
- - )} -
- ); - } else { - return
; - } +
+
{this.renderSidebarContent()}
+ + ) : ( + this.renderCollapsedSidebar(entityName, formattedMixin) + )} +
+ ); } } diff --git a/src/editor/icons/icons.jsx b/src/editor/icons/icons.jsx index 7dc55558d..81cee9772 100644 --- a/src/editor/icons/icons.jsx +++ b/src/editor/icons/icons.jsx @@ -757,6 +757,49 @@ const SignInMicrosoftIconSVG = () => ( ); +const Segment34Icon = () => ( + + + + + + + + + + +); + export { Camera32Icon, Save24Icon, @@ -791,5 +834,6 @@ export { SignInMicrosoftIconSVG, Rotate24Icon, Translate24Icon, - Object24Icon + Object24Icon, + Segment34Icon }; diff --git a/src/editor/icons/index.js b/src/editor/icons/index.js index 5f6c3ed06..eedbaff8c 100644 --- a/src/editor/icons/index.js +++ b/src/editor/icons/index.js @@ -32,5 +32,6 @@ export { SignInMicrosoftIconSVG, Rotate24Icon, Translate24Icon, - Object24Icon + Object24Icon, + Segment34Icon } from './icons.jsx'; From 327c1015252983fb1b7273b4807818d26ab48f7f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 5 Dec 2024 00:17:33 -0800 Subject: [PATCH 080/118] fix collapsed sidepanel formatting --- src/editor/components/components/Sidebar.js | 23 ++++++++++++--------- src/editor/icons/icons.jsx | 8 +++---- src/editor/icons/index.js | 2 +- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index a389b6740..e73fdbb2f 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import capitalize from 'lodash-es/capitalize'; import classnames from 'classnames'; -import { ArrowRightIcon, Object24Icon, Segment34Icon } from '../../icons'; +import { ArrowRightIcon, Object24Icon, SegmentIcon } from '../../icons'; import GeoSidebar from './GeoSidebar'; // Make sure to create and import this new component import IntersectionSidebar from './IntersectionSidebar'; import StreetSegmentSidebar from './StreetSegmentSidebar'; @@ -108,18 +108,20 @@ export default class Sidebar extends React.Component { ); } - renderCollapsedSidebar(entityName, formattedMixin) { + renderCollapsedSidebar(entity, displayName) { return (
-
- - {entityName || formattedMixin} - +
+ {displayName}
- + {entity.getAttribute('street-segment') ? ( + + ) : ( + + )}
@@ -145,6 +147,7 @@ export default class Sidebar extends React.Component { 'mt-16': true }); + const displayName = entityName || formattedMixin; return (
{this.state.showSideBar ? ( @@ -152,11 +155,11 @@ export default class Sidebar extends React.Component {
{entity.getAttribute('street-segment') ? ( - + ) : ( )} - {entityName || formattedMixin} + {displayName}
@@ -165,7 +168,7 @@ export default class Sidebar extends React.Component {
{this.renderSidebarContent()}
) : ( - this.renderCollapsedSidebar(entityName, formattedMixin) + this.renderCollapsedSidebar(entity, displayName) )}
); diff --git a/src/editor/icons/icons.jsx b/src/editor/icons/icons.jsx index 81cee9772..ff9a8f634 100644 --- a/src/editor/icons/icons.jsx +++ b/src/editor/icons/icons.jsx @@ -757,10 +757,10 @@ const SignInMicrosoftIconSVG = () => ( ); -const Segment34Icon = () => ( +const SegmentIcon = () => ( Date: Thu, 5 Dec 2024 00:26:09 -0800 Subject: [PATCH 081/118] more segment sidebar options --- .../components/StreetSegmentSidebar.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/src/editor/components/components/StreetSegmentSidebar.js b/src/editor/components/components/StreetSegmentSidebar.js index 7433e21c7..280217f64 100644 --- a/src/editor/components/components/StreetSegmentSidebar.js +++ b/src/editor/components/components/StreetSegmentSidebar.js @@ -42,6 +42,39 @@ const StreetSegmentSidebar = ({ entity }) => { isSingle={false} entity={entity} /> +
+
-----
+
+ + + )}
From 2049ca322b800452fc1d0439b981909134e292cf Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 6 Dec 2024 00:11:06 -0800 Subject: [PATCH 082/118] restore old streetmix-parsers instead, new code in 'managed-street' component --- src/aframe-streetmix-parsers.js | 1827 +++++++++++++++++++++++------- src/assets.js | 1 + src/components/managed-street.js | 810 +++++++++++++ src/index.js | 1 + 4 files changed, 2248 insertions(+), 391 deletions(-) create mode 100644 src/components/managed-street.js diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 8f4892cf2..2ac9f62ae 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -1,12 +1,55 @@ +/* global THREE */ + // Orientation - default model orientation is "outbound" (away from camera) var streetmixParsersTested = require('./tested/aframe-streetmix-parsers-tested'); var { segmentVariants } = require('./segments-variants.js'); -function getSeparatorMixinId(previousSegment, currentSegment) { - if (previousSegment === undefined || currentSegment === undefined) { - return null; +function cloneMixinAsChildren({ + objectMixinId = '', + parentEl = null, + step = 15, + radius = 60, + rotation = '0 0 0', + positionXYString = '0 0', + length = undefined, + randomY = false +}) { + for (let j = radius * -1; j <= radius; j = j + step) { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('mixin', objectMixinId); + placedObjectEl.setAttribute('class', objectMixinId); + placedObjectEl.setAttribute('position', positionXYString + ' ' + j); + + if (length) { + placedObjectEl.addEventListener('loaded', (evt) => { + evt.target.setAttribute('geometry', 'height', length); + evt.target.setAttribute('atlas-uvs', 'c', 1); + }); + } + + if (randomY) { + placedObjectEl.setAttribute( + 'rotation', + '0 ' + Math.floor(randomTestable() * 361) + ' 0' + ); + } else { + placedObjectEl.setAttribute('rotation', rotation); + } + // add the new elmement to DOM + parentEl.append(placedObjectEl); + // could be good to use geometry merger https://github.com/supermedium/superframe/tree/master/components/geometry-merger } - // Helper function to check if a segment type is "lane-ish" +} + +function randomTestable() { + return Math.random(); +} + +// this function takes a list of segments and adds lane markings or "separator segments" +// these are 0 width segments inserted into the street json prior to rendering +// the basic logic is: if there are two adjacent "lane-ish" segments, then add lane separators +function insertSeparatorSegments(segments) { + // first, let's define what is a lane that will likely need adajcent striping? function isLaneIsh(typeString) { return ( typeString.slice(typeString.length - 4) === 'lane' || @@ -16,100 +59,831 @@ function getSeparatorMixinId(previousSegment, currentSegment) { ); } - // If either segment is not lane-ish and not a divider, return null - if ( - (!isLaneIsh(previousSegment.type) && previousSegment.type !== 'divider') || - (!isLaneIsh(currentSegment.type) && currentSegment.type !== 'divider') - ) { - return null; - } + // then let's go through the segments array and build a new one with inserted separators + const newValues = segments.reduce( + (newArray, currentValue, currentIndex, arr) => { + // don't insert a lane marker before the first segment + if (currentIndex === 0) { + return newArray.concat(currentValue); + } - // Default to solid line - let variantString = 'solid-stripe'; + const previousValue = arr[currentIndex - 1]; - // Handle divider cases - if (previousSegment.type === 'divider' || currentSegment.type === 'divider') { - return variantString; - } + // if both adjacent lanes are "laneish" + if (isLaneIsh(currentValue.type) && isLaneIsh(previousValue.type)) { + // if in doubt start with a solid line + var variantString = 'solid'; - // Get directions from variant strings - const prevDirection = previousSegment.variantString.split('|')[0]; - const currDirection = currentSegment.variantString.split('|')[0]; + // if adjacent lane types are identical, then used dashed lines + if (currentValue.type === previousValue.type) { + variantString = 'dashed'; + } - // Check for opposite directions - if (prevDirection !== currDirection) { - variantString = 'solid-doubleyellow'; + // Or, if either is a drive lane or turn lane then use dashed + // Using dash vs solid for turn lanes along approach to intersections may need to be user defined + if ( + (currentValue.type === 'drive-lane' && + previousValue.type === 'turn-lane') || + (previousValue.type === 'drive-lane' && + currentValue.type === 'turn-lane') + ) { + variantString = 'dashed'; + } - // Special case for bike lanes - if ( - currentSegment.type === 'bike-lane' && - previousSegment.type === 'bike-lane' - ) { - variantString = 'short-dashed-stripe-yellow'; + // if adjacent segments in opposite directions then use double yellow + if ( + currentValue.variantString.split('|')[0] !== + previousValue.variantString.split('|')[0] + ) { + variantString = 'doubleyellow'; + // if adjacenet segments are both bike lanes, then use yellow short dash + if ( + currentValue.type === 'bike-lane' && + previousValue.type === 'bike-lane' + ) { + variantString = 'shortdashedyellow'; + } + if ( + currentValue.type === 'flex-zone' || + previousValue.type === 'flex-zone' + ) { + variantString = 'solid'; + } + } + + // special case -- if either lanes are turn lane shared, then use solid and long dash + if ( + currentValue.type === 'turn-lane' && + currentValue.variantString.split('|')[1] === 'shared' + ) { + variantString = 'soliddashedyellow'; + } else if ( + previousValue.type === 'turn-lane' && + previousValue.variantString.split('|')[1] === 'shared' + ) { + variantString = 'soliddashedyellowinverted'; + } + + // if adjacent to parking lane with markings, do not draw white line + if ( + currentValue.type === 'parking-lane' || + previousValue.type === 'parking-lane' + ) { + variantString = 'invisible'; + } + + newArray.push({ + type: 'separator', + variantString: variantString, + width: 0, + elevation: currentValue.elevation + }); + } + + // if a *lane segment and divider are adjacent, use a solid separator + if ( + (isLaneIsh(currentValue.type) && previousValue.type === 'divider') || + (isLaneIsh(previousValue.type) && currentValue.type === 'divider') + ) { + newArray.push({ + type: 'separator', + variantString: 'solid', + width: 0, + elevation: currentValue.elevation + }); + } + + newArray.push(currentValue); + return newArray; + }, + [] + ); + + // console.log('newValues =', newValues) + // console.log(segments); + + return newValues; +} + +function createStencilsParentElement(position) { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'stencils-parent'); + placedObjectEl.setAttribute('position', position); // position="1.043 0.100 -3.463" + return placedObjectEl; +} + +function createRailsElement(length, railsPosX) { + const placedObjectEl = document.createElement('a-entity'); + const railsGeometry = { + primitive: 'box', + depth: length, + width: 0.1, + height: 0.2 + }; + const railsMaterial = { + // TODO: Add environment map for reflection on metal rails + color: '#8f8f8f', + metalness: 1, + emissive: '#828282', + emissiveIntensity: 0.5, + roughness: 0.1 + }; + placedObjectEl.setAttribute('geometry', railsGeometry); + placedObjectEl.setAttribute('material', railsMaterial); + placedObjectEl.setAttribute('class', 'rails'); + placedObjectEl.setAttribute('shadow', 'receive:true; cast: true'); + placedObjectEl.setAttribute('position', railsPosX + ' 0.2 0'); // position="1.043 0.100 -3.463" + + return placedObjectEl; +} + +function createTracksParentElement(length, objectMixinId) { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'track-parent'); + placedObjectEl.setAttribute('position', '0 -0.2 0'); // position="1.043 0.100 -3.463" + // add rails + const railsWidth = { + // width as measured from center of rail, so 1/2 actual width + tram: 0.7175, // standard gauge 1,435 mm + trolley: 0.5335 // sf cable car rail gauge 1,067 mm + }; + const railsPosX = railsWidth[objectMixinId]; + placedObjectEl.append(createRailsElement(length, railsPosX)); + placedObjectEl.append(createRailsElement(length, -railsPosX)); + + return placedObjectEl; +} + +function createBollardsParentElement() { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'bollard-parent'); + return placedObjectEl; +} + +function createParentElement(className) { + const parentEl = document.createElement('a-entity'); + parentEl.setAttribute('class', className); + return parentEl; +} + +function createDividerVariant(variantName, clonedObjectRadius, step = 2.25) { + const dividerParentEl = createParentElement(`dividers-${variantName}-parent`); + cloneMixinAsChildren({ + objectMixinId: `dividers-${variantName}`, + parentEl: dividerParentEl, + step: step, + radius: clonedObjectRadius + }); + return dividerParentEl; +} + +function createClonedVariants( + variantName, + clonedObjectRadius, + step = 2.25, + rotation = '0 0 0' +) { + const dividerParentEl = createParentElement(`${variantName}-parent`); + cloneMixinAsChildren({ + objectMixinId: variantName, + parentEl: dividerParentEl, + step: step, + radius: clonedObjectRadius, + rotation: rotation + }); + return dividerParentEl; +} + +function getRandomIntInclusive(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); +} + +function getRandomArbitrary(min, max) { + return Math.random() * (max - min) + min; +} + +function getZPositions(start, end, step) { + const len = Math.floor((end - start) / step) + 1; + var arr = Array(len) + .fill() + .map((_, idx) => start + idx * step); + return arr.sort(() => 0.5 - Math.random()); +} + +function createSidewalkClonedVariants( + segmentWidthInMeters, + density, + elevationPosY = 0, + streetLength, + direction = 'random', + animated = false +) { + const xValueRange = [ + -(0.37 * segmentWidthInMeters), + 0.37 * segmentWidthInMeters + ]; + const zValueRange = getZPositions( + -0.5 * streetLength, + 0.5 * streetLength, + 1.5 + ); + const densityFactors = { + empty: 0, + sparse: 0.03, + normal: 0.125, + dense: 0.25 + }; + const totalPedestrianNumber = parseInt( + densityFactors[density] * streetLength, + 10 + ); + const dividerParentEl = createParentElement('pedestrians-parent'); + dividerParentEl.setAttribute('position', { y: elevationPosY }); + // Randomly generate avatars + for (let i = 0; i < totalPedestrianNumber; i++) { + const variantName = + animated === true + ? 'a_char' + String(getRandomIntInclusive(1, 8)) + : 'char' + String(getRandomIntInclusive(1, 16)); + const xVal = getRandomArbitrary(xValueRange[0], xValueRange[1]); + const zVal = zValueRange.pop(); + const yVal = 0; + // y = 0.2 for sidewalk elevation + const placedObjectEl = document.createElement('a-entity'); + let animationDirection = 'inbound'; + placedObjectEl.setAttribute('position', { x: xVal, y: yVal, z: zVal }); + placedObjectEl.setAttribute('mixin', variantName); + // Roughly 50% of traffic will be incoming + if (Math.random() < 0.5 && direction === 'random') { + placedObjectEl.setAttribute('rotation', '0 180 0'); + animationDirection = 'outbound'; } - // Special case for flex zones - if ( - currentSegment.type === 'flex-zone' || - previousSegment.type === 'flex-zone' - ) { - variantString = 'solid'; + if (animated) { + addLinearStreetAnimation( + placedObjectEl, + 1.4, + streetLength, + xVal, + yVal, + zVal, + animationDirection + ); } + dividerParentEl.append(placedObjectEl); + } + + return dividerParentEl; +} + +function getBikeLaneMixin(variant) { + if (variant === 'red') { + return 'surface-red bike-lane'; + } + if (variant === 'blue') { + return 'surface-blue bike-lane'; + } + if (variant === 'green') { + return 'surface-green bike-lane'; + } + return 'bike-lane'; +} + +function getBusLaneMixin(variant) { + if ((variant === 'colored') | (variant === 'red')) { + return 'surface-red bus-lane'; + } + if (variant === 'blue') { + return 'surface-blue bus-lane'; + } + if (variant === 'grass') { + return 'surface-green bus-lane'; + } + return 'bus-lane'; +} + +function getDimensions(object3d) { + var box = new THREE.Box3().setFromObject(object3d); + var x = box.max.x - box.min.x; + var y = box.max.y - box.min.y; + var z = box.max.z - box.min.z; + + return { x, y, z }; +} + +function getStartEndPosition(streetLength, objectLength) { + // get the start and end position for placing an object on a line + // computed by length of the street and object's length + const start = -0.5 * streetLength + 0.5 * objectLength; + const end = 0.5 * streetLength - 0.5 * objectLength; + return { start, end }; +} + +function randomPosition(entity, axis, length, objSizeAttr = undefined) { + // place randomly an element on a line length='length' on the axis 'axis' + // Need to call from 'model-loaded' event if objSizeAttr is undefined + // existEnts - array with existing entities (for prevent intersection) + const newObject = entity.object3D; + const objSize = objSizeAttr || getDimensions(newObject)[axis]; + const { start, end } = getStartEndPosition(length, objSize); + const setFunc = `set${axis.toUpperCase()}`; + const newPosition = getRandomArbitrary(start, end); + newObject.position[setFunc](newPosition); + return newPosition; +} + +function createChooChooElement( + variantList, + objectMixinId, + length, + showVehicles +) { + if (!showVehicles) { + return; + } + const rotationY = variantList[0] === 'inbound' ? 0 : 180; + const placedObjectEl = document.createElement('a-entity'); + const tramLength = 23; + placedObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); + placedObjectEl.setAttribute('mixin', objectMixinId); + placedObjectEl.setAttribute('class', objectMixinId); + const positionZ = randomPosition(placedObjectEl, 'z', length, tramLength); + placedObjectEl.setAttribute('position', '0 0 ' + positionZ); + return placedObjectEl; +} + +function createBusElement(variantList, length, showVehicles) { + if (!showVehicles) { + return; + } + const rotationY = variantList[0] === 'inbound' ? 0 : 180; + const busParentEl = document.createElement('a-entity'); + const busLength = 12; + const busObjectEl = document.createElement('a-entity'); + busObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); + busObjectEl.setAttribute('mixin', 'bus'); + const positionZ = randomPosition(busObjectEl, 'z', length, busLength); + busObjectEl.setAttribute('position', '0 0 ' + positionZ); + busParentEl.append(busObjectEl); + + return busParentEl; +} + +function addLinearStreetAnimation( + reusableObjectEl, + speed, + streetLength, + xPos, + yVal = 0, + zPos, + direction +) { + const totalStreetDuration = (streetLength / speed) * 1000; // time in milliseconds + const halfStreet = + direction === 'outbound' ? -streetLength / 2 : streetLength / 2; + const startingDistanceToTravel = Math.abs(halfStreet - zPos); + const startingDuration = (startingDistanceToTravel / speed) * 1000; + + const animationAttrs1 = { + property: 'position', + easing: 'linear', + loop: 'false', + from: { x: xPos, y: yVal, z: zPos }, + to: { z: halfStreet }, + dur: startingDuration + }; + const animationAttrs2 = { + property: 'position', + easing: 'linear', + loop: 'true', + from: { x: xPos, y: yVal, z: -halfStreet }, + to: { x: xPos, y: yVal, z: halfStreet }, + delay: startingDuration, + dur: totalStreetDuration + }; + reusableObjectEl.setAttribute('animation__1', animationAttrs1); + reusableObjectEl.setAttribute('animation__2', animationAttrs2); + + return reusableObjectEl; +} + +function createDriveLaneElement( + variantList, + segmentWidthInMeters, + streetLength, + animated = false, + showVehicles = true, + count = 1, + carStep = undefined +) { + if (!showVehicles) { + return; + } + let speed = 0; + let [lineVariant, direction, carType] = variantList; + if (variantList.length === 2) { + carType = direction; + direction = lineVariant; + } + + const rotationVariants = { + inbound: 0, + outbound: 180, + sideways: { + left: -90, + right: 90 + }, + 'angled-front-left': -60, + 'angled-front-right': 60, + 'angled-rear-left': -120, + 'angled-rear-right': 120 + }; + let rotationY; + if (lineVariant === 'sideways') { + rotationY = rotationVariants['sideways'][direction]; } else { - // Same direction cases - if (currentSegment.type === previousSegment.type) { - variantString = 'dashed-stripe'; - } + rotationY = rotationVariants[lineVariant]; + } - // Drive lane and turn lane combination - if ( - (currentSegment.type === 'drive-lane' && - previousSegment.type === 'turn-lane') || - (previousSegment.type === 'drive-lane' && - currentSegment.type === 'turn-lane') - ) { - variantString = 'dashed-stripe'; + if (carType === 'pedestrian') { + return createSidewalkClonedVariants( + segmentWidthInMeters, + 'normal', + 0, + streetLength, + direction, + animated + ); + } + + const driveLaneParentEl = document.createElement('a-entity'); + + if (variantList.length === 1) { + // if there is no cars + return driveLaneParentEl; + } + + const carParams = { + car: { + mixin: 'sedan-rig', + wheelDiameter: 0.76, + length: 5.17, + width: 2 + }, + microvan: { + mixin: 'suv-rig', + wheelDiameter: 0.84, + length: 5, + width: 2 + }, + truck: { + mixin: 'box-truck-rig', + wheelDiameter: 1.05, + length: 6.95, + width: 2.5 + }, + // autonomous vehicle + av: { + mixin: 'self-driving-cruise-car-rig', + wheelDiameter: 0.76, + length: 5.17, + width: 2 } + }; + + // default drive-lane variant if selected variant (carType) is not supported + if (!carParams[carType]) { + carType = 'car'; } + function createCar(positionZ = undefined, carType = 'car') { + const params = carParams[carType]; + + const reusableObjectEl = document.createElement('a-entity'); - // Special cases for shared turn lanes - const prevVariant = previousSegment.variantString.split('|')[1]; - const currVariant = currentSegment.variantString.split('|')[1]; + if (!positionZ) { + positionZ = randomPosition( + reusableObjectEl, + 'z', + streetLength, + params['length'] + ); + } + reusableObjectEl.setAttribute('position', `0 0 ${positionZ}`); + reusableObjectEl.setAttribute('mixin', params['mixin']); + reusableObjectEl.setAttribute('rotation', `0 ${rotationY} 0`); - if (currentSegment.type === 'turn-lane' && currVariant === 'shared') { - variantString = 'solid-dashed-yellow'; - } else if (previousSegment.type === 'turn-lane' && prevVariant === 'shared') { - variantString = 'solid-dashed-yellow'; + if (animated) { + speed = 5; // meters per second + reusableObjectEl.setAttribute('wheel', { + speed: speed, + wheelDiameter: params['wheelDiameter'] + }); + addLinearStreetAnimation( + reusableObjectEl, + speed, + streetLength, + 0, + 0, + positionZ, + direction + ); + } + driveLaneParentEl.append(reusableObjectEl); + return reusableObjectEl; } - // Special case for parking lanes - if ( - currentSegment.type === 'parking-lane' || - previousSegment.type === 'parking-lane' - ) { - variantString = 'invisible'; + // create one or more randomly placed cars + + if (count > 1) { + const halfStreet = streetLength / 2; + const halfParkingLength = carStep / 2 + carStep; + const allPlaces = getZPositions( + -halfStreet + halfParkingLength, + halfStreet - halfParkingLength, + carStep + ); + const randPlaces = allPlaces.slice(0, count); + const carSizeZ = + lineVariant === 'sideways' || lineVariant.includes('angled') + ? 'width' + : 'length'; + + const carSizeValueZ = carParams[carType][carSizeZ]; + + randPlaces.forEach((randPositionZ) => { + const maxDist = carStep - carSizeValueZ - 0.2; + // randOffset is for randomly displacement in a parking space (+/- maxDist) + const randOffset = -maxDist / 2 + maxDist * Math.random(); + if (maxDist > 0) { + // if the car fits in the parking space + const positionZ = randPositionZ + randOffset; + createCar(positionZ, carType); + } + }); + } else { + createCar(undefined, carType); } - return variantString; + return driveLaneParentEl; } -function getRandomIntInclusive(min, max) { - min = Math.ceil(min); - max = Math.floor(max); - return Math.floor(Math.random() * (max - min + 1) + min); +function createFoodTruckElement(variantList, length) { + const foodTruckParentEl = document.createElement('a-entity'); + + const reusableObjectEl = document.createElement('a-entity'); + const foodTruckLength = 7; + const rotationY = variantList[0] === 'left' ? 0 : 180; + reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); + reusableObjectEl.setAttribute('mixin', 'food-trailer-rig'); + + const positionZ = randomPosition( + reusableObjectEl, + 'z', + length, + foodTruckLength + ); + reusableObjectEl.setAttribute('positon', '0 0 ' + positionZ); + foodTruckParentEl.append(reusableObjectEl); + + return foodTruckParentEl; } -function getSegmentColor(variant) { - if ((variant === 'red') | (variant === 'colored')) { - return window.STREET.colors.red; +function createMagicCarpetElement(showVehicles) { + if (!showVehicles) { + return; } - if (variant === 'blue') { - return window.STREET.colors.blue; + const magicCarpetParentEl = document.createElement('a-entity'); + + const reusableObjectEl1 = document.createElement('a-entity'); + reusableObjectEl1.setAttribute('position', '0 1.75 0'); + reusableObjectEl1.setAttribute('rotation', '0 0 0'); + reusableObjectEl1.setAttribute('mixin', 'magic-carpet'); + magicCarpetParentEl.append(reusableObjectEl1); + const reusableObjectEl2 = document.createElement('a-entity'); + reusableObjectEl2.setAttribute('position', '0 1.75 0'); + reusableObjectEl2.setAttribute('rotation', '0 0 0'); + reusableObjectEl2.setAttribute('mixin', 'Character_1_M'); + magicCarpetParentEl.append(reusableObjectEl2); + + return magicCarpetParentEl; +} + +function randPlacedElements(streetLength, objLength, count) { + const placeLength = objLength / 2 + objLength; + const allPlaces = getZPositions( + -streetLength / 2 + placeLength / 2, + streetLength / 2 - placeLength / 2, + placeLength + ); + return allPlaces.slice(0, count); +} + +function createOutdoorDining(length, posY) { + const outdoorDiningParentEl = document.createElement('a-entity'); + const outdorDiningLength = 2.27; + + const randPlaces = randPlacedElements(length, outdorDiningLength, 5); + randPlaces.forEach((randPosZ) => { + const reusableObjectEl = document.createElement('a-entity'); + reusableObjectEl.setAttribute('mixin', 'outdoor_dining'); + + // const positionZ = randomPosition(reusableObjectEl, 'z', length, outdorDiningLength); + reusableObjectEl.setAttribute('position', { y: posY, z: randPosZ }); + outdoorDiningParentEl.append(reusableObjectEl); + }); + + return outdoorDiningParentEl; +} + +function createMicroMobilityElement( + variantList, + segmentType, + posY = 0, + length, + showVehicles, + animated = false +) { + if (!showVehicles) { + return; } - if ((variant === 'green') | (variant === 'grass')) { - return window.STREET.colors.green; + const microMobilityParentEl = document.createElement('a-entity'); + + const bikeLength = 2.03; + const bikeCount = getRandomIntInclusive(2, 5); + + const cyclistMixins = [ + 'cyclist-cargo', + 'cyclist1', + 'cyclist2', + 'cyclist3', + 'cyclist-dutch', + 'cyclist-kid' + ]; + + const countCyclist = cyclistMixins.length; + let mixinId = 'Bicycle_1'; + const randPlaces = randPlacedElements(length, bikeLength, bikeCount); + randPlaces.forEach((randPosZ) => { + const reusableObjectEl = document.createElement('a-entity'); + const rotationY = variantList[0] === 'inbound' ? 0 : 180; + reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); + reusableObjectEl.setAttribute('position', { y: posY, z: randPosZ }); + + if (animated) { + reusableObjectEl.setAttribute('animation-mixer', ''); + const speed = 5; + addLinearStreetAnimation( + reusableObjectEl, + speed, + length, + 0, + posY, + randPosZ, + variantList[0] + ); + } + if (segmentType === 'bike-lane') { + mixinId = cyclistMixins[getRandomIntInclusive(0, countCyclist)]; + } else { + mixinId = 'ElectricScooter_1'; + } + + reusableObjectEl.setAttribute('mixin', mixinId); + microMobilityParentEl.append(reusableObjectEl); + }); + + return microMobilityParentEl; +} + +function createFlexZoneElement(variantList, length, showVehicles = true) { + if (!showVehicles) { + return; } - return window.STREET.colors.white; + const flexZoneParentEl = document.createElement('a-entity'); + const carLength = 5; + const carCount = getRandomIntInclusive(2, 4); + const randPlaces = randPlacedElements(length, carLength, carCount); + randPlaces.forEach((randPosZ) => { + const reusableObjectEl = document.createElement('a-entity'); + const rotationY = variantList[1] === 'inbound' ? 0 : 180; + reusableObjectEl.setAttribute('rotation', '0 ' + rotationY + ' 0'); + if (variantList[0] === 'taxi') { + reusableObjectEl.setAttribute('mixin', 'sedan-taxi-rig'); + } else if (variantList[0] === 'rideshare') { + reusableObjectEl.setAttribute('mixin', 'sedan-rig'); + } + reusableObjectEl.setAttribute('position', { z: randPosZ }); + flexZoneParentEl.append(reusableObjectEl); + }); + + return flexZoneParentEl; +} + +function createWayfindingElements() { + const wayfindingParentEl = document.createElement('a-entity'); + let reusableObjectEl; + + reusableObjectEl = document.createElement('a-entity'); + reusableObjectEl.setAttribute('position', '0 1 0'); + reusableObjectEl.setAttribute('mixin', 'wayfinding-box'); + wayfindingParentEl.append(reusableObjectEl); + + reusableObjectEl = document.createElement('a-entity'); + reusableObjectEl.setAttribute('position', '0 1.2 0.06'); + reusableObjectEl.setAttribute( + 'geometry', + 'primitive: plane; width: 0.8; height: 1.6' + ); + reusableObjectEl.setAttribute('material', 'src:#wayfinding-map'); + wayfindingParentEl.append(reusableObjectEl); + + reusableObjectEl = document.createElement('a-entity'); + reusableObjectEl.setAttribute('position', '0 1.2 -0.06'); + reusableObjectEl.setAttribute('rotation', '0 180 0'); + reusableObjectEl.setAttribute( + 'geometry', + 'primitive: plane; width: 0.8; height: 1.6' + ); + reusableObjectEl.setAttribute('material', 'src:#wayfinding-map'); + wayfindingParentEl.append(reusableObjectEl); + + return wayfindingParentEl; +} + +function createBenchesParentElement() { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'bench-parent'); + // y = 0.2 for sidewalk elevation + placedObjectEl.setAttribute('position', '0 0.2 3.5'); + return placedObjectEl; +} + +function createBikeRacksParentElement(posY) { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'bikerack-parent'); + placedObjectEl.setAttribute('position', { y: posY, z: -3.5 }); + return placedObjectEl; +} + +function createBikeShareStationElement(variantList, posY) { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'bikeshare'); + placedObjectEl.setAttribute('mixin', 'bikeshare'); + const rotationCloneY = variantList[0] === 'left' ? 90 : 270; + placedObjectEl.setAttribute('rotation', '0 ' + rotationCloneY + ' 0'); + placedObjectEl.setAttribute('position', { y: posY }); + return placedObjectEl; +} + +function createParkletElement(length, variantList) { + const parkletParent = document.createElement('a-entity'); + const parkletLength = 4.03; + const parkletCount = 3; + const randPlaces = randPlacedElements(length, parkletLength, parkletCount); + randPlaces.forEach((randPosZ) => { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'parklet'); + placedObjectEl.setAttribute('position', { x: 0, y: 0.02, z: randPosZ }); + placedObjectEl.setAttribute('mixin', 'parklet'); + const rotationY = variantList[0] === 'left' ? 90 : 270; + placedObjectEl.setAttribute('rotation', { y: rotationY }); + parkletParent.append(placedObjectEl); + }); + return parkletParent; +} + +function createTreesParentElement() { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'tree-parent'); + // y = 0.2 for sidewalk elevation + placedObjectEl.setAttribute('position', '0 0.2 7'); + return placedObjectEl; +} + +function createLampsParentElement() { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'lamp-parent'); + // y = 0.2 for sidewalk elevation + placedObjectEl.setAttribute('position', '0 0.2 0'); // position="1.043 0.100 -3.463" + return placedObjectEl; +} + +function createBusStopElement(rotationBusStopY, posY) { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'bus-stop'); + placedObjectEl.setAttribute('rotation', '0 ' + rotationBusStopY + ' 0'); + placedObjectEl.setAttribute('mixin', 'bus-stop'); + placedObjectEl.setAttribute('position', { y: posY }); + return placedObjectEl; +} + +function createBrtStationElement() { + const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'brt-station'); + placedObjectEl.setAttribute('mixin', 'brt-station'); + return placedObjectEl; } // offset to center the street around global x position of 0 @@ -124,6 +898,72 @@ function createCenteredStreetElement(segments) { return streetEl; } +function createSegmentElement( + segmentWidthInMeters, + positionY, + mixinId, + length, + repeatCount, + elevation = 0 +) { + var segmentEl = document.createElement('a-entity'); + const heightLevels = [0.2, 0.4, 0.6]; + const height = heightLevels[elevation]; + if (elevation === 0) { + positionY = -0.1; + } else if (elevation === 2) { + positionY = 0.1; + } + + segmentEl.setAttribute( + 'geometry', + `primitive: box; + height: ${height}; + depth: ${length}; + width: ${segmentWidthInMeters};` + ); + + segmentEl.setAttribute('position', { y: positionY }); + segmentEl.setAttribute('mixin', mixinId); + + if (repeatCount.length !== 0) { + segmentEl.setAttribute( + 'material', + `repeat: ${repeatCount[0]} ${repeatCount[1]}` + ); + } + + return segmentEl; +} + +function createSeparatorElement( + positionY, + rotationY, + mixinId, + length, + repeatCount, + elevation = 0 +) { + var segmentEl = document.createElement('a-entity'); + const scaleY = length / 150; + const scalePlane = '1 ' + scaleY + ' 1'; + + segmentEl.setAttribute('rotation', '270 ' + rotationY + ' 0'); + segmentEl.setAttribute('scale', scalePlane); + + segmentEl.setAttribute('position', '0 ' + positionY + ' 0'); + segmentEl.setAttribute('mixin', mixinId); + + if (repeatCount.length !== 0) { + segmentEl.setAttribute( + 'material', + `repeat: ${repeatCount[0]} ${repeatCount[1]}` + ); + } + + return segmentEl; +} + // show warning message if segment or variantString are not supported function supportCheck(segmentType, segmentVariantString) { if (segmentType === 'separator') return; @@ -155,6 +995,16 @@ function processSegments( globalAnimated, showVehicles ) { + var clonedObjectRadius = length / 2; + // Adjust clonedObjectRadius so that objects do not repeat + if (length > 12) { + clonedObjectRadius = (length - 12) / 2; + } + // add additional 0-width segments for stripes (painted markers) + if (showStriping) { + segments = insertSeparatorSegments(segments); + } + // create and center offset to center the street around global x position of 0 var streetParentEl = createCenteredStreetElement(segments); streetParentEl.classList.add('street-parent'); @@ -163,7 +1013,6 @@ function processSegments( var cumulativeWidthInMeters = 0; for (var i = 0; i < segments.length; i++) { - var segmentColor = null; var segmentParentEl = document.createElement('a-entity'); segmentParentEl.classList.add('segment-parent-' + i); @@ -172,6 +1021,7 @@ function processSegments( cumulativeWidthInMeters = cumulativeWidthInMeters + segmentWidthInMeters; var segmentPositionX = cumulativeWidthInMeters - 0.5 * segmentWidthInMeters; + var positionY = 0; // get variantString var variantList = segments[i].variantString @@ -184,74 +1034,91 @@ function processSegments( // elevation property from streetmix segment const elevation = segments[i].elevation; - var direction = - variantList[0] === 'inbound' || variantList[1] === 'inbound' - ? 'inbound' - : 'outbound'; + const elevationLevels = [0, 0.2, 0.4]; + const elevationPosY = elevationLevels[elevation]; + + // add y elevation position as a data attribute to segment entity + segmentParentEl.setAttribute('data-elevation-posY', elevationPosY); + + // Note: segment 3d models are outbound by default + // If segment variant inbound, rotate segment model by 180 degrees + var rotationY = + variantList[0] === 'inbound' || variantList[1] === 'inbound' ? 180 : 0; + var isOutbound = + variantList[0] === 'outbound' || variantList[1] === 'outbound' ? 1 : -1; // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that - var segmentPreset = segments[i].type; + var groundMixinId = segments[i].type; + + // repeat value for material property - repeatCount[0] is x texture repeat and repeatCount[1] is y texture repeat + const repeatCount = []; // look at segment type and variant(s) to determine specific cases if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { - segmentParentEl.setAttribute( - 'street-generated-stencil', - `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15; direction: ${direction}` - ); + // make a parent entity for the stencils + const stencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015 + }); + // clone a bunch of stencil entities (note: this is not draw call efficient) + cloneMixinAsChildren({ + objectMixinId: 'stencils sharrow', + parentEl: stencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 10, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(stencilsParentEl); } else if ( segments[i].type === 'bike-lane' || segments[i].type === 'scooter' ) { - segmentPreset = 'bike-lane'; // use bike lane road material + // make a parent entity for the stencils + const stencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015 + }); // get the mixin id for a bike lane - segmentColor = getSegmentColor(variantList[1]); - segmentParentEl.setAttribute( - 'street-generated-stencil', - `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20; direction: ${direction};` - ); - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; - modelsArray: cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid${segments[i].type === 'scooter' ? 'ElectricScooter_1' : ''}; - length: ${length}; - spacing: 2.03; - direction: ${direction}; - count: ${getRandomIntInclusive(2, 5)};` + groundMixinId = getBikeLaneMixin(variantList[1]); + // clone a bunch of stencil entities (note: this is not draw call efficient) + cloneMixinAsChildren({ + objectMixinId: 'stencils bike-arrow', + parentEl: stencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 20, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(stencilsParentEl); + segmentParentEl.append( + createMicroMobilityElement( + variantList, + segments[i].type, + elevationPosY, + length, + showVehicles, + globalAnimated + ) ); } else if ( segments[i].type === 'light-rail' || segments[i].type === 'streetcar' ) { - segmentPreset = 'rail'; - // get the color for a bus lane - segmentColor = getSegmentColor(variantList[1]); + // get the mixin id for a bus lane + groundMixinId = getBusLaneMixin(variantList[1]); // get the mixin id for the vehicle (is it a trolley or a tram?) - const objectMixinId = - segments[i].type === 'streetcar' ? 'trolley' : 'tram'; - if (showVehicles) { - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 20; direction: ${direction}; count: 1;` - ); - } - segmentParentEl.setAttribute( - 'street-generated-rail', - `length: ${length}; gauge: ${segments[i].type === 'streetcar' ? 1067 : 1435};` + var objectMixinId = segments[i].type === 'streetcar' ? 'trolley' : 'tram'; + // create and append a train element + segmentParentEl.append( + createChooChooElement(variantList, objectMixinId, length, showVehicles) ); + // make the parent for all the objects to be cloned + const tracksParentEl = createTracksParentElement(length, objectMixinId); + // add these trains to the segment parent + segmentParentEl.append(tracksParentEl); } else if (segments[i].type === 'turn-lane') { - segmentPreset = 'drive-lane'; // use normal drive lane road material - if (showVehicles && variantList[1] !== 'shared') { - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; - modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; - length: ${length}; - spacing: 7.3; - direction: ${direction}; - count: ${getRandomIntInclusive(2, 4)};` - ); - } + groundMixinId = 'drive-lane'; // use normal drive lane road material var markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name + // Fix streetmix inbound turn lane orientation (change left to right) per: https://github.com/streetmix/streetmix/issues/683 if (variantList[0] === 'inbound') { markerMixinId = markerMixinId.replace(/left|right/g, function (m) { @@ -264,303 +1131,472 @@ function processSegments( if (variantList[1] === 'left-right-straight') { markerMixinId = 'all'; } - segmentParentEl.setAttribute( - 'street-generated-stencil', - `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.4; spacing: 20; direction: ${direction};` - ); + var mixinString = 'stencils ' + markerMixinId; + + // make the parent for all the objects to be cloned + const stencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015 + }); + cloneMixinAsChildren({ + objectMixinId: mixinString, + parentEl: stencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 15, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(stencilsParentEl); if (variantList[1] === 'shared') { - segmentParentEl.setAttribute( - 'street-generated-stencil__2', - `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.6; spacing: 20; direction: ${direction}; facing: 180;` - ); + // add an additional marking to represent the opposite turn marking stencil (rotated 180º) + const stencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015, + z: -3 * isOutbound + }); + cloneMixinAsChildren({ + objectMixinId: mixinString, + parentEl: stencilsParentEl, + rotation: '-90 ' + (rotationY + 180) + ' 0', + step: 15, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(stencilsParentEl); } } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { - segmentPreset = 'divider'; + groundMixinId = 'divider'; // make some bollards - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: bollard; spacing: 4; length: ${length}` - ); + const bollardsParentEl = createBollardsParentElement(); + cloneMixinAsChildren({ + objectMixinId: 'bollard', + parentEl: bollardsParentEl, + step: 4, + radius: clonedObjectRadius + }); + // add the bollards to the segment parent + segmentParentEl.append(bollardsParentEl); + repeatCount[0] = 1; + repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { - segmentPreset = 'grass'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: dividers-flowers; spacing: 2.25; length: ${length}` + groundMixinId = 'grass'; + segmentParentEl.append( + createDividerVariant('flowers', clonedObjectRadius, 2.25) ); } else if ( segments[i].type === 'divider' && variantList[0] === 'planting-strip' ) { - segmentPreset = 'grass'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: dividers-planting-strip; spacing: 2.25; length: ${length}` + groundMixinId = 'grass'; + segmentParentEl.append( + createDividerVariant('planting-strip', clonedObjectRadius, 2.25) ); } else if ( segments[i].type === 'divider' && variantList[0] === 'planter-box' ) { - segmentPreset = 'grass'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: dividers-planter-box; spacing: 2.45; length: ${length}` + groundMixinId = 'grass'; + segmentParentEl.append( + createDividerVariant('planter-box', clonedObjectRadius, 2.45) ); } else if ( segments[i].type === 'divider' && variantList[0] === 'palm-tree' ) { - segmentPreset = 'grass'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: palm-tree; length: ${length}` - ); + groundMixinId = 'grass'; + const treesParentEl = createTreesParentElement(); + cloneMixinAsChildren({ + objectMixinId: 'palm-tree', + parentEl: treesParentEl, + randomY: true, + radius: clonedObjectRadius + }); + segmentParentEl.append(treesParentEl); } else if ( segments[i].type === 'divider' && variantList[0] === 'big-tree' ) { - segmentPreset = 'grass'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: tree3; length: ${length}` - ); + groundMixinId = 'grass'; + const treesParentEl = createTreesParentElement(); + cloneMixinAsChildren({ + objectMixinId: 'tree3', + parentEl: treesParentEl, + randomY: true, + radius: clonedObjectRadius + }); + segmentParentEl.append(treesParentEl); } else if (segments[i].type === 'divider' && variantList[0] === 'bush') { - segmentPreset = 'grass'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: dividers-bush; spacing: 2.25; length: ${length}` + groundMixinId = 'grass'; + segmentParentEl.append( + createDividerVariant('bush', clonedObjectRadius, 2.25) ); } else if (segments[i].type === 'divider' && variantList[0] === 'dome') { - segmentPreset = 'divider'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: dividers-dome; spacing: 2.25; length: ${length}` + groundMixinId = 'divider'; + segmentParentEl.append( + createDividerVariant('dome', clonedObjectRadius, 2.25) ); + repeatCount[0] = 1; + repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider') { - segmentPreset = 'divider'; + groundMixinId = 'divider'; + repeatCount[0] = 1; + repeatCount[1] = parseInt(length) / 4; } else if ( segments[i].type === 'temporary' && variantList[0] === 'barricade' ) { - segmentPreset = 'drive-lane'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: temporary-barricade; spacing: 2.25; length: ${length}` + groundMixinId = 'drive-lane'; + segmentParentEl.append( + createClonedVariants('temporary-barricade', clonedObjectRadius, 2.25) ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'traffic-cone' ) { - segmentPreset = 'drive-lane'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: temporary-traffic-cone; spacing: 2.25; length: ${length}` + groundMixinId = 'drive-lane'; + segmentParentEl.append( + createClonedVariants('temporary-traffic-cone', clonedObjectRadius, 2.25) ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'jersey-barrier-plastic' ) { - segmentPreset = 'drive-lane'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: jersey-barrier-plastic; spacing: 2.25; length: ${length}` + groundMixinId = 'drive-lane'; + segmentParentEl.append( + createClonedVariants( + 'temporary-jersey-barrier-plastic', + clonedObjectRadius, + 2.25 + ) ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'jersey-barrier-concrete' ) { - segmentPreset = 'drive-lane'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: temporary-jersey-barrier-concrete; spacing: 2.93; length: ${length}` + groundMixinId = 'drive-lane'; + segmentParentEl.append( + createClonedVariants( + 'temporary-jersey-barrier-concrete', + clonedObjectRadius, + 2.93 + ) ); } else if ( segments[i].type === 'bus-lane' || segments[i].type === 'brt-lane' ) { - segmentPreset = 'bus-lane'; - // get the color for a bus lane - segmentColor = getSegmentColor(variantList[1]); - - if (showVehicles) { - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; model: bus; length: ${length}; spacing: 15; direction: ${direction}; count: 1;` - ); - } - segmentParentEl.setAttribute( - 'street-generated-stencil', - `stencils: word-only, word-taxi, word-bus; length: ${length}; spacing: 40; padding: 10; direction: ${direction}` + groundMixinId = getBusLaneMixin(variantList[1]); + + segmentParentEl.append( + createBusElement(variantList, length, showVehicles) ); + + // create parent for the bus lane stencils to rotate the phrase instead of the word + let reusableObjectStencilsParentEl; + + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015 + }); + cloneMixinAsChildren({ + objectMixinId: 'stencils word-bus', + parentEl: reusableObjectStencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 50, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(reusableObjectStencilsParentEl); + + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015, + z: 10 + }); + cloneMixinAsChildren({ + objectMixinId: 'stencils word-taxi', + parentEl: reusableObjectStencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 50, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(reusableObjectStencilsParentEl); + + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015, + z: 20 + }); + cloneMixinAsChildren({ + objectMixinId: 'stencils word-only', + parentEl: reusableObjectStencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 50, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(reusableObjectStencilsParentEl); } else if (segments[i].type === 'drive-lane') { - if (showVehicles) { - // const isAnimated = variantList[2] === 'animated' || globalAnimated; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; - modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; - length: ${length}; - spacing: 7.3; - direction: ${direction}; - count: ${getRandomIntInclusive(2, 4)};` - ); - } - } else if (segments[i].type === 'food-truck') { - segmentPreset = 'drive-lane'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; model: food-trailer-rig; length: ${length}; spacing: 7; direction: ${direction}; count: 2;` + const isAnimated = variantList[2] === 'animated' || globalAnimated; + const count = getRandomIntInclusive(2, 3); + const carStep = 7.3; + segmentParentEl.append( + createDriveLaneElement( + variantList, + segmentWidthInMeters, + length, + isAnimated, + showVehicles, + count, + carStep + ) ); + } else if (segments[i].type === 'food-truck') { + groundMixinId = 'drive-lane'; + segmentParentEl.append(createFoodTruckElement(variantList, length)); } else if (segments[i].type === 'flex-zone') { - segmentPreset = 'parking-lane'; - if (showVehicles) { - const objectMixinId = - variantList[0] === 'taxi' ? 'sedan-taxi-rig' : 'sedan-rig'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 6; direction: ${direction}; count: 4;` - ); - } - segmentParentEl.setAttribute( - 'street-generated-stencil', - `stencils: word-loading-small, word-only-small; length: ${length}; spacing: 40; padding: 10; direction: ${direction}` + groundMixinId = 'bright-lane'; + segmentParentEl.append( + createFlexZoneElement(variantList, length, showVehicles) ); + + let reusableObjectStencilsParentEl; + + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015, + z: 5 + }); + cloneMixinAsChildren({ + objectMixinId: 'stencils word-loading-small', + parentEl: reusableObjectStencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 50, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(reusableObjectStencilsParentEl); + + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015, + z: -5 + }); + cloneMixinAsChildren({ + objectMixinId: 'stencils word-only-small', + parentEl: reusableObjectStencilsParentEl, + rotation: '-90 ' + rotationY + ' 0', + step: 50, + radius: clonedObjectRadius + }); + // add this stencil stuff to the segment parent + segmentParentEl.append(reusableObjectStencilsParentEl); } else if (segments[i].type === 'sidewalk' && variantList[0] !== 'empty') { - segmentParentEl.setAttribute( - 'street-generated-pedestrians', - `segmentWidth: ${segmentWidthInMeters}; density: ${variantList[0]}; length: ${length};` + // handles variantString with value sparse, normal, or dense sidewalk + const isAnimated = variantList[1] === 'animated' || globalAnimated; + segmentParentEl.append( + createSidewalkClonedVariants( + segmentWidthInMeters, + variantList[0], + elevationPosY, + length, + 'random', + isAnimated + ) ); } else if (segments[i].type === 'sidewalk-wayfinding') { - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: single; model: wayfinding; length: ${length};` - ); + segmentParentEl.append(createWayfindingElements()); } else if (segments[i].type === 'sidewalk-bench') { + // make the parent for all the benches + const benchesParentEl = createBenchesParentElement(); + const rotationCloneY = variantList[0] === 'right' ? -90 : 90; if (variantList[0] === 'center') { - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: bench_orientation_center; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` - ); + cloneMixinAsChildren({ + objectMixinId: 'bench_orientation_center', + parentEl: benchesParentEl, + rotation: '0 ' + rotationCloneY + ' 0', + radius: clonedObjectRadius + }); + // add benches to the segment parent + segmentParentEl.append(benchesParentEl); } else { // `right` or `left` bench - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: bench; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` - ); + cloneMixinAsChildren({ + objectMixinId: 'bench', + parentEl: benchesParentEl, + rotation: '0 ' + rotationCloneY + ' 0', + radius: clonedObjectRadius + }); + // add benches to the segment parent + segmentParentEl.append(benchesParentEl); } } else if (segments[i].type === 'sidewalk-bike-rack') { + // make the parent for all the bike racks + const bikeRacksParentEl = createBikeRacksParentElement(elevationPosY); + const rotationCloneY = variantList[1] === 'sidewalk-parallel' ? 90 : 0; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: bikerack; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.2` - ); + cloneMixinAsChildren({ + objectMixinId: 'bikerack', + parentEl: bikeRacksParentEl, + rotation: '0 ' + rotationCloneY + ' 0', + radius: clonedObjectRadius + }); + // add bike racks to the segment parent + segmentParentEl.append(bikeRacksParentEl); } else if (segments[i].type === 'magic-carpet') { - segmentPreset = 'drive-lane'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: single; model: magic-carpet; - length: ${length}; - positionY: 1.2;` - ); - segmentParentEl.setAttribute( - 'street-generated-clones__2', - `mode: single; model: Character_1_M; - length: ${length}; - positionY: 1.2;` - ); + groundMixinId = 'drive-lane'; + segmentParentEl.append(createMagicCarpetElement(showVehicles)); } else if (segments[i].type === 'outdoor-dining') { - segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; model: outdoor_dining; length: ${length}; spacing: 3; count: 5;` - ); + groundMixinId = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; + segmentParentEl.append(createOutdoorDining(length, elevationPosY)); } else if (segments[i].type === 'parklet') { - segmentPreset = 'drive-lane'; - const rotationCloneY = variantList[0] === 'left' ? 90 : 270; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; model: parklet; length: ${length}; spacing: 5.5; count: 3; facing: ${rotationCloneY};` - ); + groundMixinId = 'drive-lane'; + segmentParentEl.append(createParkletElement(length, variantList)); } else if (segments[i].type === 'bikeshare') { - const rotationCloneY = variantList[0] === 'left' ? 90 : 270; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: single; model: bikeshare; length: ${length}; facing: ${rotationCloneY}; justify: middle;` + // make the parent for all the stations + segmentParentEl.append( + createBikeShareStationElement(variantList, elevationPosY) ); } else if (segments[i].type === 'utilities') { - const rotationCloneY = variantList[0] === 'right' ? 180 : 0; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotationCloneY}` + var rotation = variantList[0] === 'right' ? '0 180 0' : '0 0 0'; + const utilityPoleElems = createClonedVariants( + 'utility_pole', + clonedObjectRadius, + 15, + rotation ); + segmentParentEl.append(utilityPoleElems); } else if (segments[i].type === 'sidewalk-tree') { - const objectMixinId = - variantList[0] === 'palm-tree' ? 'palm-tree' : 'tree3'; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: ${objectMixinId}; length: ${length}; randomFacing: true;` - ); + // make the parent for all the trees + const treesParentEl = createTreesParentElement(); + if (variantList[0] === 'palm-tree') { + objectMixinId = 'palm-tree'; + } else { + objectMixinId = 'tree3'; + } + // clone a bunch of trees under the parent + cloneMixinAsChildren({ + objectMixinId: objectMixinId, + parentEl: treesParentEl, + randomY: true, + radius: clonedObjectRadius + }); + segmentParentEl.append(treesParentEl); } else if ( segments[i].type === 'sidewalk-lamp' && (variantList[1] === 'modern' || variantList[1] === 'pride') ) { + // Make the parent object for all the lamps + const lampsParentEl = createLampsParentElement(); if (variantList[0] === 'both') { - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: lamp-modern-double; length: ${length}; cycleOffset: 0.4;` - ); + cloneMixinAsChildren({ + objectMixinId: 'lamp-modern-double', + parentEl: lampsParentEl, + rotation: '0 0 0', + radius: clonedObjectRadius + }); + segmentParentEl.append(lampsParentEl); } else { var rotationCloneY = variantList[0] === 'right' ? 0 : 180; - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: lamp-modern; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.4;` - ); + cloneMixinAsChildren({ + objectMixinId: 'lamp-modern', + parentEl: lampsParentEl, + rotation: '0 ' + rotationCloneY + ' 0', + radius: clonedObjectRadius + }); + segmentParentEl.append(lampsParentEl); } // Add the pride flags to the lamp posts if ( variantList[1] === 'pride' && (variantList[0] === 'right' || variantList[0] === 'both') ) { - segmentParentEl.setAttribute( - 'street-generated-clones__2', - `model: pride-flag; length: ${length}; cycleOffset: 0.4; positionX: 0.409; positionY: 5;` - ); + cloneMixinAsChildren({ + objectMixinId: 'pride-flag', + parentEl: lampsParentEl, + positionXYString: '0.409 5', + radius: clonedObjectRadius + }); } if ( variantList[1] === 'pride' && (variantList[0] === 'left' || variantList[0] === 'both') ) { - segmentParentEl.setAttribute( - 'street-generated-clones__2', - `model: pride-flag; length: ${length}; facing: 180; cycleOffset: 0.4; positionX: -0.409; positionY: 5;` - ); + cloneMixinAsChildren({ + objectMixinId: 'pride-flag', + parentEl: lampsParentEl, + rotation: '0 -180 0', + positionXYString: '-0.409 5', + radius: clonedObjectRadius + }); } } else if ( segments[i].type === 'sidewalk-lamp' && variantList[1] === 'traditional' ) { - segmentParentEl.setAttribute( - 'street-generated-clones', - `model: lamp-traditional; length: ${length};` - ); + // make the parent for all the lamps + const lampsParentEl = createLampsParentElement(); + // clone a bunch of lamps under the parent + cloneMixinAsChildren({ + objectMixinId: 'lamp-traditional', + parentEl: lampsParentEl, + radius: clonedObjectRadius + }); + segmentParentEl.append(lampsParentEl); } else if (segments[i].type === 'transit-shelter') { var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: single; model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` + segmentParentEl.append( + createBusStopElement(rotationBusStopY, elevationPosY) ); } else if (segments[i].type === 'brt-station') { - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: single; model: brt-station; length: ${length};` - ); + segmentParentEl.append(createBrtStationElement()); + } else if ( + segments[i].type === 'separator' && + variantList[0] === 'dashed' + ) { + groundMixinId = 'markings dashed-stripe'; + positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + // for all markings material property repeat = "1 25". So every 150/25=6 meters put a dash + repeatCount[0] = 1; + repeatCount[1] = parseInt(length / 6); + } else if (segments[i].type === 'separator' && variantList[0] === 'solid') { + groundMixinId = 'markings solid-stripe'; + positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + } else if ( + segments[i].type === 'separator' && + variantList[0] === 'doubleyellow' + ) { + groundMixinId = 'markings solid-doubleyellow'; + positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + } else if ( + segments[i].type === 'separator' && + variantList[0] === 'shortdashedyellow' + ) { + groundMixinId = 'markings yellow short-dashed-stripe'; + positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + // for short-dashed-stripe every 3 meters put a dash + repeatCount[0] = 1; + repeatCount[1] = parseInt(length / 3); + } else if ( + segments[i].type === 'separator' && + variantList[0] === 'soliddashedyellow' + ) { + groundMixinId = 'markings yellow solid-dashed'; + positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + } else if ( + segments[i].type === 'separator' && + variantList[0] === 'soliddashedyellowinverted' + ) { + groundMixinId = 'markings yellow solid-dashed'; + positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + rotationY = '180'; + repeatCount[0] = 1; + repeatCount[1] = parseInt(length / 6); } else if (segments[i].type === 'parking-lane') { - segmentPreset = 'parking-lane'; + let reusableObjectStencilsParentEl; + + groundMixinId = 'bright-lane'; let parkingMixin = 'stencils parking-t'; + + const carCount = 5; let carStep = 6; const rotationVars = { - // markings rotation outbound: 90, inbound: 90, sideways: 0, @@ -585,102 +1621,101 @@ function processSegments( carStep = 3; markingLength = segmentWidthInMeters; markingPosX = 0; - parkingMixin = 'solid-stripe'; - if (variantList[1] === 'right') { - // make sure cars face the right way on right side - markingsRotZ = markingsRotZ + 180; - } + parkingMixin = 'markings solid-stripe'; } - segmentParentEl.setAttribute( - 'street-generated-clones', - `mode: random; - modelsArray: sedan-rig, self-driving-waymo-car, suv-rig; - length: ${length}; - spacing: ${carStep}; - count: ${getRandomIntInclusive(6, 8)}; - facing: ${markingsRotZ - 90};` + const markingPosXY = markingPosX + ' 0'; + const clonedStencilRadius = length / 2 - carStep; + + segmentParentEl.append( + createDriveLaneElement( + [...variantList, 'car'], + segmentWidthInMeters, + length, + false, + showVehicles, + carCount, + carStep + ) ); if (variantList[1] === 'left') { - segmentParentEl.setAttribute( - 'street-generated-stencil', - `model: ${parkingMixin}; length: ${length}; cycleOffset: 1; spacing: ${carStep}; positionX: ${markingPosX}; facing: ${markingsRotZ + 90}; stencilHeight: ${markingLength};` - ); + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015 + }); + cloneMixinAsChildren({ + objectMixinId: parkingMixin, + parentEl: reusableObjectStencilsParentEl, + positionXYString: markingPosXY, + rotation: '-90 ' + '90 ' + markingsRotZ, + length: markingLength, + step: carStep, + radius: clonedStencilRadius + }); } else { - segmentParentEl.setAttribute( - 'street-generated-stencil', - `model: ${parkingMixin}; length: ${length}; cycleOffset: 1; spacing: ${carStep}; positionX: ${markingPosX}; facing: ${markingsRotZ + 90}; stencilHeight: ${markingLength};` - ); + reusableObjectStencilsParentEl = createStencilsParentElement({ + y: elevationPosY + 0.015 + }); + cloneMixinAsChildren({ + objectMixinId: parkingMixin, + parentEl: reusableObjectStencilsParentEl, + positionXYString: markingPosXY, + rotation: '-90 ' + '90 ' + markingsRotZ, + length: markingLength, + step: carStep, + radius: clonedStencilRadius + }); } + // add the stencils to the segment parent + segmentParentEl.append(reusableObjectStencilsParentEl); } - // if this thing is a sidewalk, make segmentPreset sidewalk if (streetmixParsersTested.isSidewalk(segments[i].type)) { - segmentPreset = 'sidewalk'; + groundMixinId = 'sidewalk'; + repeatCount[0] = segmentWidthInMeters / 1.5; + // every 2 meters repeat sidewalk texture + repeatCount[1] = parseInt(length / 2); } // add new object - segmentParentEl.setAttribute('street-segment', 'type', segmentPreset); - segmentParentEl.setAttribute( - 'street-segment', - 'width', - segmentWidthInMeters - ); - segmentParentEl.setAttribute('street-segment', 'length', length); - segmentParentEl.setAttribute('street-segment', 'level', elevation); - segmentParentEl.setAttribute('street-segment', 'direction', direction); - segmentParentEl.setAttribute( - // find default color for segmentPreset - 'street-segment', - 'color', - segmentColor ?? window.STREET.types[segmentPreset]?.color // no error handling for segmentPreset not found - ); - segmentParentEl.setAttribute( - // find default surface type for segmentPreset - 'street-segment', - 'surface', - window.STREET.types[segmentPreset]?.surface // no error handling for segmentPreset not found - ); - - let currentSegment = segments[i]; - let previousSegment = segments[i - 1]; - let separatorMixinId = getSeparatorMixinId(previousSegment, currentSegment); - - if (separatorMixinId && showStriping) { - segmentParentEl.setAttribute( - 'street-generated-striping', - `striping: ${separatorMixinId}; length: ${length}; segmentWidth: ${segmentWidthInMeters};` + if (segments[i].type !== 'separator') { + segmentParentEl.append( + createSegmentElement( + segmentWidthInMeters, + positionY, + groundMixinId, + length, + repeatCount, + elevation + ) + ); + } else { + segmentParentEl.append( + createSeparatorElement( + positionY, + rotationY, + groundMixinId, + length, + repeatCount, + elevation + ) ); - // if previous segment is turn lane and shared, then facing should be 180 - if ( - previousSegment && - previousSegment.type === 'turn-lane' && - previousSegment.variantString.split('|')[1] === 'shared' - ) { - segmentParentEl.setAttribute( - 'street-generated-striping', - 'facing', - 180 - ); - } } - + // returns JSON output instead // append the new surfaceElement to the segmentParentEl streetParentEl.append(segmentParentEl); segmentParentEl.setAttribute('position', segmentPositionX + ' 0 0'); segmentParentEl.setAttribute( 'data-layer-name', - '' + segments[i].type + ' • ' + variantList[0] + 'Segment • ' + segments[i].type + ', ' + variantList[0] ); } - // create new brown box to represent ground underneath street const dirtBox = document.createElement('a-box'); const xPos = cumulativeWidthInMeters / 2; - dirtBox.setAttribute('position', `${xPos} -1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 + dirtBox.setAttribute('position', `${xPos} -1.1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 dirtBox.setAttribute('width', cumulativeWidthInMeters); dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side - dirtBox.setAttribute('material', `color: ${STREET.colors.brown};`); + dirtBox.setAttribute('material', 'color: #664B00;'); dirtBox.setAttribute('data-layer-name', 'Underground'); streetParentEl.append(dirtBox); return streetParentEl; @@ -690,6 +1725,7 @@ module.exports.processSegments = processSegments; // test - for streetObject of street 44 and buildingElementId render 2 building sides function processBuildings(left, right, streetWidth, showGround, length) { const buildingElement = document.createElement('a-entity'); + const clonedObjectRadius = 0.45 * length; buildingElement.classList.add('buildings-parent'); buildingElement.setAttribute( 'data-layer-name', @@ -820,6 +1856,7 @@ function processBuildings(left, right, streetWidth, showGround, length) { if (currentValue === 'waterfront' || currentValue === 'compound-wall') { const objectPositionX = buildingPositionX - (sideMultiplier * 150) / 2; const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'seawall-parent'); placedObjectEl.setAttribute('position', { x: objectPositionX, z: 4.5 }); // position="1.043 0.100 -3.463" let rotationCloneY; if (currentValue === 'compound-wall') { @@ -831,26 +1868,34 @@ function processBuildings(left, right, streetWidth, showGround, length) { } else { rotationCloneY = side === 'left' ? -90 : 90; } - placedObjectEl.setAttribute('data-layer-name', 'seawall-parent-' + side); - placedObjectEl.setAttribute( - 'street-generated-clones', - `model: seawall; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.8;` - ); + placedObjectEl.classList.add('seawall-parent-' + side); buildingElement.appendChild(placedObjectEl); + // clone a bunch of seawalls under the parent + cloneMixinAsChildren({ + objectMixinId: 'seawall', + parentEl: placedObjectEl, + rotation: '0 ' + rotationCloneY + ' 0', + step: 15, + radius: clonedObjectRadius + }); } if (currentValue === 'fence' || currentValue === 'parking-lot') { const objectPositionX = buildingPositionX - (sideMultiplier * 150) / 2; // make the parent for all the objects to be cloned const placedObjectEl = document.createElement('a-entity'); + placedObjectEl.setAttribute('class', 'fence-parent'); placedObjectEl.setAttribute('position', objectPositionX + ' 0 4.625'); // position="1.043 0.100 -3.463" - placedObjectEl.setAttribute('data-layer-name', 'fence-parent'); + placedObjectEl.classList.add('fence-parent-' + buildingPositionX); // clone a bunch of fences under the parent const rotationCloneY = side === 'right' ? -90 : 90; - placedObjectEl.setAttribute( - 'street-generated-clones', - `model: fence; length: ${length}; spacing: 9.25; facing: ${rotationCloneY}; cycleOffset: 1` - ); + cloneMixinAsChildren({ + objectMixinId: 'fence', + parentEl: placedObjectEl, + rotation: '0 ' + rotationCloneY + ' 0', + step: 9.25, + radius: clonedObjectRadius + }); buildingElement.appendChild(placedObjectEl); } }); diff --git a/src/assets.js b/src/assets.js index a389662c1..5862e0591 100644 --- a/src/assets.js +++ b/src/assets.js @@ -136,6 +136,7 @@ function buildAssetHTML(assetUrl, categories) { `, 'segment-colors': ` + diff --git a/src/components/managed-street.js b/src/components/managed-street.js new file mode 100644 index 000000000..955f66b2a --- /dev/null +++ b/src/components/managed-street.js @@ -0,0 +1,810 @@ +/* global AFRAME */ + +// Orientation - default model orientation is "outbound" (away from camera) +var { segmentVariants } = require('../segments-variants.js'); +var streetmixUtils = require('../tested/streetmix-utils'); +var streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested'); + +// invoking from js console +/* +userLayersEl = document.getElementById('street-container'); +newStreetEl = document.createElement('a-entity'); +newStreetEl.setAttribute('managed-street', 'sourceType: streetmix-url; sourceValue: https://streetmix.net/kfarr/3/'); +userLayersEl.append(newStreetEl); +// press enter and then run this line +newStreetEl.components['managed-street'].refreshFromSource(); +*/ + +AFRAME.registerComponent('managed-street', { + schema: { + width: { + type: 'array' + }, + length: { + type: 'string', + default: '60' + }, + sourceType: { + type: 'string', + oneOf: ['streetmix-url', 'streetplan-url', 'json-blob'] + }, + sourceValue: { + type: 'string' + }, + sourceId: { + type: 'string' + }, + showVehicles: { + type: 'boolean', + default: true + }, + showStriping: { + type: 'boolean', + default: true + } + }, + init: function () { + this.createdEntities = []; + // Bind the method to preserve context + this.refreshFromSource = this.refreshFromSource.bind(this); + }, + refreshFromSource: function () { + const self = this; + const data = self.data; + if (data.sourceType === 'streetmix-url') { + this.loadAndParseStreetmixURL(data.sourceValue); + } else if (data.sourceType === 'streetplan-url') { + this.refreshFromStreetplanURL(data.sourceValue); + } else if (data.sourceType === 'json-blob') { + this.refreshFromJSONBlob(data.sourceValue); + } + }, + loadAndParseStreetmixURL: function (streetmixURL) { + const data = this.data; + const streetmixAPIURL = streetmixUtils.streetmixUserToAPI(streetmixURL); + console.log( + '[managed-street] loader', + 'sourceType: `streetmix-url`, setting `streetmixAPIURL` to', + streetmixAPIURL + ); + + const request = new XMLHttpRequest(); + console.log('[managed-street] loader', 'GET ' + streetmixAPIURL); + + request.open('GET', streetmixAPIURL, true); + + // Bind the component instance to use inside the callbacks + const self = this; + + request.onload = function () { + if (this.status >= 200 && this.status < 400) { + // Connection success + const streetmixResponseObject = JSON.parse(this.response); + // convert units of measurement if necessary + const streetData = streetmixUtils.convertStreetValues( + streetmixResponseObject.data.street + ); + const streetmixSegments = streetData.segments; + + const streetmixName = streetmixResponseObject.name; + + self.el.setAttribute('data-layer-name', 'Street • ' + streetmixName); + + console.log('streetmixSegments', streetmixSegments); + const streetEl = parseStreetmixSegments( + streetmixSegments, + data.showStriping, + data.length, + data.globalAnimated, // remove + data.showVehicles + ); + self.el.append(streetEl); + + const streetWidth = streetmixSegments.reduce( + (streetWidth, segmentData) => streetWidth + segmentData.width, + 0 + ); + + self.el.setAttribute('managed-street', 'width', streetWidth); + } else { + // We reached our target server, but it returned an error + console.log( + '[streetmix-loader]', + 'Loading Error: We reached the target server, but it returned an error' + ); + } + }; + request.onerror = function () { + // There was a connection error of some sort + console.log( + '[streetmix-loader]', + 'Loading Error: There was a connection error of some sort' + ); + }; + request.send(); + } +}); + +function getSeparatorMixinId(previousSegment, currentSegment) { + if (previousSegment === undefined || currentSegment === undefined) { + return null; + } + // Helper function to check if a segment type is "lane-ish" + function isLaneIsh(typeString) { + return ( + typeString.slice(typeString.length - 4) === 'lane' || + typeString === 'light-rail' || + typeString === 'streetcar' || + typeString === 'flex-zone' + ); + } + + // If either segment is not lane-ish and not a divider, return null + if ( + (!isLaneIsh(previousSegment.type) && previousSegment.type !== 'divider') || + (!isLaneIsh(currentSegment.type) && currentSegment.type !== 'divider') + ) { + return null; + } + + // Default to solid line + let variantString = 'solid-stripe'; + + // Handle divider cases + if (previousSegment.type === 'divider' || currentSegment.type === 'divider') { + return variantString; + } + + // Get directions from variant strings + const prevDirection = previousSegment.variantString.split('|')[0]; + const currDirection = currentSegment.variantString.split('|')[0]; + + // Check for opposite directions + if (prevDirection !== currDirection) { + variantString = 'solid-doubleyellow'; + + // Special case for bike lanes + if ( + currentSegment.type === 'bike-lane' && + previousSegment.type === 'bike-lane' + ) { + variantString = 'short-dashed-stripe-yellow'; + } + + // Special case for flex zones + if ( + currentSegment.type === 'flex-zone' || + previousSegment.type === 'flex-zone' + ) { + variantString = 'solid'; + } + } else { + // Same direction cases + if (currentSegment.type === previousSegment.type) { + variantString = 'dashed-stripe'; + } + + // Drive lane and turn lane combination + if ( + (currentSegment.type === 'drive-lane' && + previousSegment.type === 'turn-lane') || + (previousSegment.type === 'drive-lane' && + currentSegment.type === 'turn-lane') + ) { + variantString = 'dashed-stripe'; + } + } + + // Special cases for shared turn lanes + const prevVariant = previousSegment.variantString.split('|')[1]; + const currVariant = currentSegment.variantString.split('|')[1]; + + if (currentSegment.type === 'turn-lane' && currVariant === 'shared') { + variantString = 'solid-dashed-yellow'; + } else if (previousSegment.type === 'turn-lane' && prevVariant === 'shared') { + variantString = 'solid-dashed-yellow'; + } + + // Special case for parking lanes + if ( + currentSegment.type === 'parking-lane' || + previousSegment.type === 'parking-lane' + ) { + variantString = 'invisible'; + } + + return variantString; +} + +function getRandomIntInclusive(min, max) { + min = Math.ceil(min); + max = Math.floor(max); + return Math.floor(Math.random() * (max - min + 1) + min); +} + +function getSegmentColor(variant) { + if ((variant === 'red') | (variant === 'colored')) { + return window.STREET.colors.red; + } + if (variant === 'blue') { + return window.STREET.colors.blue; + } + if ((variant === 'green') | (variant === 'grass')) { + return window.STREET.colors.green; + } + return window.STREET.colors.white; +} + +// offset to center the street around global x position of 0 +function createCenteredStreetElement(segments) { + const streetEl = document.createElement('a-entity'); + const streetWidth = segments.reduce( + (streetWidth, segmentData) => streetWidth + segmentData.width, + 0 + ); + const offset = 0 - streetWidth / 2; + streetEl.setAttribute('position', offset + ' 0 0'); + return streetEl; +} + +// show warning message if segment or variantString are not supported +function supportCheck(segmentType, segmentVariantString) { + if (segmentType === 'separator') return; + // variants supported in 3DStreet + const supportedVariants = segmentVariants[segmentType]; + if (!supportedVariants) { + STREET.notify.warningMessage( + `The '${segmentType}' segment type is not yet supported in 3DStreet` + ); + console.log( + `The '${segmentType}' segment type is not yet supported in 3DStreet` + ); + } else if (!supportedVariants.includes(segmentVariantString)) { + STREET.notify.warningMessage( + `The '${segmentVariantString}' variant of segment '${segmentType}' is not yet supported in 3DStreet` + ); + console.log( + `The '${segmentVariantString}' variant of segment '${segmentType}' is not yet supported in 3DStreet` + ); + } +} + +// OLD: takes a street's `segments` (array) from streetmix and a `streetElementId` (string) and places objects to make up a street with all segments +// NEW: takes a `segments` (array) from streetmix and return an element and its children which represent the 3D street scene +function parseStreetmixSegments( + segments, + showStriping, + length, + globalAnimated, + showVehicles +) { + // create and center offset to center the street around global x position of 0 + var streetParentEl = createCenteredStreetElement(segments); + streetParentEl.classList.add('street-parent'); + streetParentEl.setAttribute('data-layer-name', 'Street Segments Container'); + streetParentEl.setAttribute('data-no-transform', ''); + + var cumulativeWidthInMeters = 0; + for (var i = 0; i < segments.length; i++) { + var segmentColor = null; + var segmentParentEl = document.createElement('a-entity'); + segmentParentEl.classList.add('segment-parent-' + i); + + var segmentWidthInMeters = segments[i].width; + // console.log('Type: ' + segments[i].type + '; Width: ' + segmentWidthInFeet + 'ft / ' + segmentWidthInMeters + 'm'); + + cumulativeWidthInMeters = cumulativeWidthInMeters + segmentWidthInMeters; + var segmentPositionX = cumulativeWidthInMeters - 0.5 * segmentWidthInMeters; + + // get variantString + var variantList = segments[i].variantString + ? segments[i].variantString.split('|') + : ''; + + // show warning message if segment or variantString are not supported + supportCheck(segments[i].type, segments[i].variantString); + + // elevation property from streetmix segment + const elevation = segments[i].elevation; + + var direction = + variantList[0] === 'inbound' || variantList[1] === 'inbound' + ? 'inbound' + : 'outbound'; + + // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that + var segmentPreset = segments[i].type; + + // look at segment type and variant(s) to determine specific cases + if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: sharrow; length: ${length}; cycleOffset: 0.2; spacing: 15; direction: ${direction}` + ); + } else if ( + segments[i].type === 'bike-lane' || + segments[i].type === 'scooter' + ) { + segmentPreset = 'bike-lane'; // use bike lane road material + // get the mixin id for a bike lane + segmentColor = getSegmentColor(variantList[1]); + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: bike-arrow; length: ${length}; cycleOffset: 0.3; spacing: 20; direction: ${direction};` + ); + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; + modelsArray: cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid${segments[i].type === 'scooter' ? 'ElectricScooter_1' : ''}; + length: ${length}; + spacing: 2.03; + direction: ${direction}; + count: ${getRandomIntInclusive(2, 5)};` + ); + } else if ( + segments[i].type === 'light-rail' || + segments[i].type === 'streetcar' + ) { + segmentPreset = 'rail'; + // get the color for a bus lane + segmentColor = getSegmentColor(variantList[1]); + // get the mixin id for the vehicle (is it a trolley or a tram?) + const objectMixinId = + segments[i].type === 'streetcar' ? 'trolley' : 'tram'; + if (showVehicles) { + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 20; direction: ${direction}; count: 1;` + ); + } + segmentParentEl.setAttribute( + 'street-generated-rail', + `length: ${length}; gauge: ${segments[i].type === 'streetcar' ? 1067 : 1435};` + ); + } else if (segments[i].type === 'turn-lane') { + segmentPreset = 'drive-lane'; // use normal drive lane road material + if (showVehicles && variantList[1] !== 'shared') { + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; + modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + length: ${length}; + spacing: 7.3; + direction: ${direction}; + count: ${getRandomIntInclusive(2, 4)};` + ); + } + var markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name + // Fix streetmix inbound turn lane orientation (change left to right) per: https://github.com/streetmix/streetmix/issues/683 + if (variantList[0] === 'inbound') { + markerMixinId = markerMixinId.replace(/left|right/g, function (m) { + return m === 'left' ? 'right' : 'left'; + }); + } + if (variantList[1] === 'shared') { + markerMixinId = 'left'; + } + if (variantList[1] === 'left-right-straight') { + markerMixinId = 'all'; + } + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.4; spacing: 20; direction: ${direction};` + ); + if (variantList[1] === 'shared') { + segmentParentEl.setAttribute( + 'street-generated-stencil__2', + `model: ${markerMixinId}; length: ${length}; cycleOffset: 0.6; spacing: 20; direction: ${direction}; facing: 180;` + ); + } + } else if (segments[i].type === 'divider' && variantList[0] === 'bollard') { + segmentPreset = 'divider'; + // make some bollards + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: bollard; spacing: 4; length: ${length}` + ); + } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: dividers-flowers; spacing: 2.25; length: ${length}` + ); + } else if ( + segments[i].type === 'divider' && + variantList[0] === 'planting-strip' + ) { + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: dividers-planting-strip; spacing: 2.25; length: ${length}` + ); + } else if ( + segments[i].type === 'divider' && + variantList[0] === 'planter-box' + ) { + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: dividers-planter-box; spacing: 2.45; length: ${length}` + ); + } else if ( + segments[i].type === 'divider' && + variantList[0] === 'palm-tree' + ) { + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: palm-tree; length: ${length}` + ); + } else if ( + segments[i].type === 'divider' && + variantList[0] === 'big-tree' + ) { + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: tree3; length: ${length}` + ); + } else if (segments[i].type === 'divider' && variantList[0] === 'bush') { + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: dividers-bush; spacing: 2.25; length: ${length}` + ); + } else if (segments[i].type === 'divider' && variantList[0] === 'dome') { + segmentPreset = 'divider'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: dividers-dome; spacing: 2.25; length: ${length}` + ); + } else if (segments[i].type === 'divider') { + segmentPreset = 'divider'; + } else if ( + segments[i].type === 'temporary' && + variantList[0] === 'barricade' + ) { + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: temporary-barricade; spacing: 2.25; length: ${length}` + ); + } else if ( + segments[i].type === 'temporary' && + variantList[0] === 'traffic-cone' + ) { + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: temporary-traffic-cone; spacing: 2.25; length: ${length}` + ); + } else if ( + segments[i].type === 'temporary' && + variantList[0] === 'jersey-barrier-plastic' + ) { + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: jersey-barrier-plastic; spacing: 2.25; length: ${length}` + ); + } else if ( + segments[i].type === 'temporary' && + variantList[0] === 'jersey-barrier-concrete' + ) { + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: temporary-jersey-barrier-concrete; spacing: 2.93; length: ${length}` + ); + } else if ( + segments[i].type === 'bus-lane' || + segments[i].type === 'brt-lane' + ) { + segmentPreset = 'bus-lane'; + // get the color for a bus lane + segmentColor = getSegmentColor(variantList[1]); + + if (showVehicles) { + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; model: bus; length: ${length}; spacing: 15; direction: ${direction}; count: 1;` + ); + } + segmentParentEl.setAttribute( + 'street-generated-stencil', + `stencils: word-only, word-taxi, word-bus; length: ${length}; spacing: 40; padding: 10; direction: ${direction}` + ); + } else if (segments[i].type === 'drive-lane') { + if (showVehicles) { + // const isAnimated = variantList[2] === 'animated' || globalAnimated; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; + modelsArray: sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike; + length: ${length}; + spacing: 7.3; + direction: ${direction}; + count: ${getRandomIntInclusive(2, 4)};` + ); + } + } else if (segments[i].type === 'food-truck') { + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; model: food-trailer-rig; length: ${length}; spacing: 7; direction: ${direction}; count: 2;` + ); + } else if (segments[i].type === 'flex-zone') { + segmentPreset = 'parking-lane'; + if (showVehicles) { + const objectMixinId = + variantList[0] === 'taxi' ? 'sedan-taxi-rig' : 'sedan-rig'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; model: ${objectMixinId}; length: ${length}; spacing: 6; direction: ${direction}; count: 4;` + ); + } + segmentParentEl.setAttribute( + 'street-generated-stencil', + `stencils: word-loading-small, word-only-small; length: ${length}; spacing: 40; padding: 10; direction: ${direction}` + ); + } else if (segments[i].type === 'sidewalk' && variantList[0] !== 'empty') { + segmentParentEl.setAttribute( + 'street-generated-pedestrians', + `segmentWidth: ${segmentWidthInMeters}; density: ${variantList[0]}; length: ${length};` + ); + } else if (segments[i].type === 'sidewalk-wayfinding') { + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: single; model: wayfinding; length: ${length};` + ); + } else if (segments[i].type === 'sidewalk-bench') { + const rotationCloneY = variantList[0] === 'right' ? -90 : 90; + if (variantList[0] === 'center') { + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: bench_orientation_center; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` + ); + } else { + // `right` or `left` bench + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: bench; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.1` + ); + } + } else if (segments[i].type === 'sidewalk-bike-rack') { + const rotationCloneY = variantList[1] === 'sidewalk-parallel' ? 90 : 0; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: bikerack; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.2` + ); + } else if (segments[i].type === 'magic-carpet') { + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: single; model: magic-carpet; + length: ${length}; + positionY: 1.2;` + ); + segmentParentEl.setAttribute( + 'street-generated-clones__2', + `mode: single; model: Character_1_M; + length: ${length}; + positionY: 1.2;` + ); + } else if (segments[i].type === 'outdoor-dining') { + segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; model: outdoor_dining; length: ${length}; spacing: 3; count: 5;` + ); + } else if (segments[i].type === 'parklet') { + segmentPreset = 'drive-lane'; + const rotationCloneY = variantList[0] === 'left' ? 90 : 270; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; model: parklet; length: ${length}; spacing: 5.5; count: 3; facing: ${rotationCloneY};` + ); + } else if (segments[i].type === 'bikeshare') { + const rotationCloneY = variantList[0] === 'left' ? 90 : 270; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: single; model: bikeshare; length: ${length}; facing: ${rotationCloneY}; justify: middle;` + ); + } else if (segments[i].type === 'utilities') { + const rotationCloneY = variantList[0] === 'right' ? 180 : 0; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotationCloneY}` + ); + } else if (segments[i].type === 'sidewalk-tree') { + const objectMixinId = + variantList[0] === 'palm-tree' ? 'palm-tree' : 'tree3'; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: ${objectMixinId}; length: ${length}; randomFacing: true;` + ); + } else if ( + segments[i].type === 'sidewalk-lamp' && + (variantList[1] === 'modern' || variantList[1] === 'pride') + ) { + if (variantList[0] === 'both') { + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: lamp-modern-double; length: ${length}; cycleOffset: 0.4;` + ); + } else { + var rotationCloneY = variantList[0] === 'right' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: lamp-modern; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.4;` + ); + } + // Add the pride flags to the lamp posts + if ( + variantList[1] === 'pride' && + (variantList[0] === 'right' || variantList[0] === 'both') + ) { + segmentParentEl.setAttribute( + 'street-generated-clones__2', + `model: pride-flag; length: ${length}; cycleOffset: 0.4; positionX: 0.409; positionY: 5;` + ); + } + if ( + variantList[1] === 'pride' && + (variantList[0] === 'left' || variantList[0] === 'both') + ) { + segmentParentEl.setAttribute( + 'street-generated-clones__2', + `model: pride-flag; length: ${length}; facing: 180; cycleOffset: 0.4; positionX: -0.409; positionY: 5;` + ); + } + } else if ( + segments[i].type === 'sidewalk-lamp' && + variantList[1] === 'traditional' + ) { + segmentParentEl.setAttribute( + 'street-generated-clones', + `model: lamp-traditional; length: ${length};` + ); + } else if (segments[i].type === 'transit-shelter') { + var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: single; model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` + ); + } else if (segments[i].type === 'brt-station') { + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: single; model: brt-station; length: ${length};` + ); + } else if (segments[i].type === 'parking-lane') { + segmentPreset = 'parking-lane'; + let parkingMixin = 'stencils parking-t'; + let carStep = 6; + + const rotationVars = { + // markings rotation + outbound: 90, + inbound: 90, + sideways: 0, + 'angled-front-left': 30, + 'angled-front-right': -30, + 'angled-rear-left': -30, + 'angled-rear-right': 30 + }; + let markingsRotZ = rotationVars[variantList[0]]; + let markingLength; + + // calculate position X and rotation Z for T-markings + let markingPosX = segmentWidthInMeters / 2; + if (markingsRotZ === 90 && variantList[1] === 'right') { + markingsRotZ = -90; + markingPosX = -markingPosX + 0.75; + } else { + markingPosX = markingPosX - 0.75; + } + + if (variantList[0] === 'sideways' || variantList[0].includes('angled')) { + carStep = 3; + markingLength = segmentWidthInMeters; + markingPosX = 0; + parkingMixin = 'solid-stripe'; + if (variantList[1] === 'right') { + // make sure cars face the right way on right side + markingsRotZ = markingsRotZ + 180; + } + } + segmentParentEl.setAttribute( + 'street-generated-clones', + `mode: random; + modelsArray: sedan-rig, self-driving-waymo-car, suv-rig; + length: ${length}; + spacing: ${carStep}; + count: ${getRandomIntInclusive(6, 8)}; + facing: ${markingsRotZ - 90};` + ); + if (variantList[1] === 'left') { + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: ${parkingMixin}; length: ${length}; cycleOffset: 1; spacing: ${carStep}; positionX: ${markingPosX}; facing: ${markingsRotZ + 90}; stencilHeight: ${markingLength};` + ); + } else { + segmentParentEl.setAttribute( + 'street-generated-stencil', + `model: ${parkingMixin}; length: ${length}; cycleOffset: 1; spacing: ${carStep}; positionX: ${markingPosX}; facing: ${markingsRotZ + 90}; stencilHeight: ${markingLength};` + ); + } + } + + // if this thing is a sidewalk, make segmentPreset sidewalk + if (streetmixParsersTested.isSidewalk(segments[i].type)) { + segmentPreset = 'sidewalk'; + } + + // add new object + segmentParentEl.setAttribute('street-segment', 'type', segmentPreset); + segmentParentEl.setAttribute( + 'street-segment', + 'width', + segmentWidthInMeters + ); + segmentParentEl.setAttribute('street-segment', 'length', length); + segmentParentEl.setAttribute('street-segment', 'level', elevation); + segmentParentEl.setAttribute('street-segment', 'direction', direction); + segmentParentEl.setAttribute( + // find default color for segmentPreset + 'street-segment', + 'color', + segmentColor ?? window.STREET.types[segmentPreset]?.color // no error handling for segmentPreset not found + ); + segmentParentEl.setAttribute( + // find default surface type for segmentPreset + 'street-segment', + 'surface', + window.STREET.types[segmentPreset]?.surface // no error handling for segmentPreset not found + ); + + let currentSegment = segments[i]; + let previousSegment = segments[i - 1]; + let separatorMixinId = getSeparatorMixinId(previousSegment, currentSegment); + + if (separatorMixinId && showStriping) { + segmentParentEl.setAttribute( + 'street-generated-striping', + `striping: ${separatorMixinId}; length: ${length}; segmentWidth: ${segmentWidthInMeters};` + ); + // if previous segment is turn lane and shared, then facing should be 180 + if ( + previousSegment && + previousSegment.type === 'turn-lane' && + previousSegment.variantString.split('|')[1] === 'shared' + ) { + segmentParentEl.setAttribute( + 'street-generated-striping', + 'facing', + 180 + ); + } + } + + // append the new surfaceElement to the segmentParentEl + streetParentEl.append(segmentParentEl); + segmentParentEl.setAttribute('position', segmentPositionX + ' 0 0'); + segmentParentEl.setAttribute( + 'data-layer-name', + '' + segments[i].type + ' • ' + variantList[0] + ); + } + + // create new brown box to represent ground underneath street + const dirtBox = document.createElement('a-box'); + const xPos = cumulativeWidthInMeters / 2; + dirtBox.setAttribute('position', `${xPos} -1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 + dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 + dirtBox.setAttribute('width', cumulativeWidthInMeters); + dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side + dirtBox.setAttribute('material', `color: ${STREET.colors.brown};`); + dirtBox.setAttribute('data-layer-name', 'Underground'); + streetParentEl.append(dirtBox); + return streetParentEl; +} diff --git a/src/index.js b/src/index.js index e3271165f..cdbb7b76f 100644 --- a/src/index.js +++ b/src/index.js @@ -22,6 +22,7 @@ require('./components/street-environment.js'); require('./components/intersection.js'); require('./components/obb-clipping.js'); require('./components/street-segment.js'); +require('./components/managed-street.js'); require('./components/street-generated-stencil.js'); require('./components/street-generated-striping.js'); require('./components/street-generated-pedestrians.js'); From 4e5eb819df44ea662ca2b2efa565fbb66f237c09 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Fri, 6 Dec 2024 09:37:00 -0800 Subject: [PATCH 083/118] add managed street layer --- src/components/managed-street.js | 11 ++++++++ .../AddLayerPanel/createLayerFunctions.js | 26 +++++++++++++++++++ .../components/AddLayerPanel/layersData.js | 13 +++++++++- 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 955f66b2a..312aae705 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -34,6 +34,10 @@ AFRAME.registerComponent('managed-street', { sourceId: { type: 'string' }, + synchronize: { + type: 'boolean', + default: false + }, showVehicles: { type: 'boolean', default: true @@ -48,6 +52,13 @@ AFRAME.registerComponent('managed-street', { // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); }, + update: function () { + const data = this.data; + if (data.synchronize) { + this.refreshFromSource(); + this.el.setAttribute('managed-street', 'synchronize', false); + } + }, refreshFromSource: function () { const self = this; const data = self.data; diff --git a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js index 8f0653ee2..7a92c83b1 100644 --- a/src/editor/components/components/AddLayerPanel/createLayerFunctions.js +++ b/src/editor/components/components/AddLayerPanel/createLayerFunctions.js @@ -50,6 +50,32 @@ export function createMapbox() { }); } +export function createManagedStreet(position) { + // This creates a new Managed Street + let streetmixURL = prompt( + 'Please enter a Streetmix URL', + 'https://streetmix.net/kfarr/3/3dstreet-demo-street' + ); + + if (streetmixURL && streetmixURL !== '') { + const definition = { + id: createUniqueId(), + components: { + position: position ?? '0 0 0', + 'managed-street': { + sourceType: 'streetmix-url', + sourceValue: streetmixURL, + showVehicles: true, + showStriping: true, + synchronize: true + } + } + }; + + AFRAME.INSPECTOR.execute('entitycreate', definition); + } +} + export function createStreetmixStreet(position, streetmixURL, hideBuildings) { // This code snippet allows the creation of an additional Streetmix street // in your 3DStreet scene without replacing any existing streets. diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 5ac902825..753f09e62 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -9,7 +9,8 @@ import { create80ftRightOfWay, create94ftRightOfWay, create150ftRightOfWay, - createImageEntity + createImageEntity, + createManagedStreet } from './createLayerFunctions'; export const streetLayersData = [ @@ -114,5 +115,15 @@ export const customLayersData = [ 'Place an image such as a sign, reference photo, custom map, etc.', id: 4, handlerFunction: createImageEntity + }, + { + name: 'Create Managed Street (Beta)', + img: '', + requiresPro: true, + icon: '', + description: + 'Create a new street from Streetmix using the Managed Street component.', + id: 5, + handlerFunction: createManagedStreet } ]; From 82996b1ae0d06a27eb92d10701be3916813d13c9 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 7 Dec 2024 16:02:35 -0800 Subject: [PATCH 084/118] manage segment x positions with justification --- src/components/managed-street.js | 202 ++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 56 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 312aae705..173801f68 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -9,16 +9,14 @@ var streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested' /* userLayersEl = document.getElementById('street-container'); newStreetEl = document.createElement('a-entity'); -newStreetEl.setAttribute('managed-street', 'sourceType: streetmix-url; sourceValue: https://streetmix.net/kfarr/3/'); +newStreetEl.setAttribute('managed-street', 'sourceType: streetmix-url; sourceValue: https://streetmix.net/kfarr/3/; synchronize: true'); userLayersEl.append(newStreetEl); -// press enter and then run this line -newStreetEl.components['managed-street'].refreshFromSource(); */ AFRAME.registerComponent('managed-street', { schema: { width: { - type: 'array' + type: 'number' }, length: { type: 'string', @@ -45,19 +43,39 @@ AFRAME.registerComponent('managed-street', { showStriping: { type: 'boolean', default: true + }, + justifyWidth: { + default: 'center', + type: 'string', + oneOf: ['center', 'left', 'right'] + }, + justifyLength: { + default: 'start', + type: 'string', + oneOf: ['middle', 'start', 'end'] } }, init: function () { this.createdEntities = []; + this.pendingEntities = []; // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); }, - update: function () { + update: function (oldData) { const data = this.data; if (data.synchronize) { - this.refreshFromSource(); this.el.setAttribute('managed-street', 'synchronize', false); + this.refreshFromSource(); } + if ( + oldData.justifyWidth !== data.justifyWidth || + oldData.justifyLength !== data.justifyLength + ) { + this.applyJustification(); + 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 }, refreshFromSource: function () { const self = this; @@ -70,6 +88,88 @@ AFRAME.registerComponent('managed-street', { this.refreshFromJSONBlob(data.sourceValue); } }, + applyJustification: function () { + const data = this.data; + const segmentEls = this.createdEntities; + 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; + }); + }, + createOrUpdateJustifiedDirtBox: function () { + const data = this.data; + const streetWidth = data.width; + if (!streetWidth) { + return; + } + console.log('streetWidth', streetWidth); + const streetLength = data.length; + if (!this.justifiedDirtBox) { + // create new brown box to represent ground underneath street + const dirtBox = document.createElement('a-box'); + // dirtBox.setAttribute('position', `${xPosition} -1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 + dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 + dirtBox.setAttribute('width', streetWidth); + dirtBox.setAttribute('depth', streetLength - 0.2); // depth is length - 0.1 on each side + dirtBox.setAttribute('material', `color: ${window.STREET.colors.brown};`); + dirtBox.setAttribute('data-layer-name', 'Underground'); + dirtBox.classList.add('dirtbox'); + this.el.append(dirtBox); + this.justifiedDirtBox = dirtBox; + } + + // set starting xPosition for width justification + let xPosition = 0; // default for center justified + if (data.justifyWidth === 'left') { + xPosition = streetWidth / 2; + } + if (data.justifyWidth === 'right') { + xPosition = -streetWidth / 2; + } + + // 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; + } + + this.justifiedDirtBox.setAttribute( + 'position', + `${xPosition} -1 ${zPosition}` + ); + }, loadAndParseStreetmixURL: function (streetmixURL) { const data = this.data; const streetmixAPIURL = streetmixUtils.streetmixUserToAPI(streetmixURL); @@ -100,23 +200,36 @@ AFRAME.registerComponent('managed-street', { const streetmixName = streetmixResponseObject.name; self.el.setAttribute('data-layer-name', 'Street • ' + streetmixName); + const streetWidth = streetmixSegments.reduce( + (streetWidth, segmentData) => streetWidth + segmentData.width, + 0 + ); + self.el.setAttribute('managed-street', 'width', streetWidth); - console.log('streetmixSegments', streetmixSegments); - const streetEl = parseStreetmixSegments( + const segmentEls = parseStreetmixSegments( streetmixSegments, data.showStriping, data.length, - data.globalAnimated, // remove data.showVehicles ); - self.el.append(streetEl); - - const streetWidth = streetmixSegments.reduce( - (streetWidth, segmentData) => streetWidth + segmentData.width, - 0 - ); - - self.el.setAttribute('managed-street', 'width', streetWidth); + self.el.append(...segmentEls); + + self.pendingEntities = segmentEls; + // for each pending entity Listen for loaded event + for (const entity of self.pendingEntities) { + entity.addEventListener('loaded', () => { + self.onEntityLoaded(entity); + }); + } + // Set up a promise that resolves when all entities are loaded + self.allLoadedPromise = new Promise((resolve) => { + self.resolveAllLoaded = resolve; + }); + // When all entities are loaded, do something with them + self.allLoadedPromise.then(() => { + self.applyJustification(); + self.createOrUpdateJustifiedDirtBox(); + }); } else { // We reached our target server, but it returned an error console.log( @@ -133,6 +246,18 @@ AFRAME.registerComponent('managed-street', { ); }; request.send(); + }, + onEntityLoaded: function (entity) { + // Remove from pending set + const index = this.pendingEntities.indexOf(entity); + if (index > -1) { + this.pendingEntities.splice(index, 1); + } + this.createdEntities.push(entity); + // If no more pending entities, resolve the promise + if (this.pendingEntities.length === 0) { + this.resolveAllLoaded(); + } } }); @@ -246,18 +371,6 @@ function getSegmentColor(variant) { return window.STREET.colors.white; } -// offset to center the street around global x position of 0 -function createCenteredStreetElement(segments) { - const streetEl = document.createElement('a-entity'); - const streetWidth = segments.reduce( - (streetWidth, segmentData) => streetWidth + segmentData.width, - 0 - ); - const offset = 0 - streetWidth / 2; - streetEl.setAttribute('position', offset + ' 0 0'); - return streetEl; -} - // show warning message if segment or variantString are not supported function supportCheck(segmentType, segmentVariantString) { if (segmentType === 'separator') return; @@ -282,18 +395,9 @@ function supportCheck(segmentType, segmentVariantString) { // OLD: takes a street's `segments` (array) from streetmix and a `streetElementId` (string) and places objects to make up a street with all segments // NEW: takes a `segments` (array) from streetmix and return an element and its children which represent the 3D street scene -function parseStreetmixSegments( - segments, - showStriping, - length, - globalAnimated, - showVehicles -) { +function parseStreetmixSegments(segments, showStriping, length, showVehicles) { // create and center offset to center the street around global x position of 0 - var streetParentEl = createCenteredStreetElement(segments); - streetParentEl.classList.add('street-parent'); - streetParentEl.setAttribute('data-layer-name', 'Street Segments Container'); - streetParentEl.setAttribute('data-no-transform', ''); + var segmentEls = []; var cumulativeWidthInMeters = 0; for (var i = 0; i < segments.length; i++) { @@ -527,7 +631,6 @@ function parseStreetmixSegments( ); } else if (segments[i].type === 'drive-lane') { if (showVehicles) { - // const isAnimated = variantList[2] === 'animated' || globalAnimated; segmentParentEl.setAttribute( 'street-generated-clones', `mode: random; @@ -797,25 +900,12 @@ function parseStreetmixSegments( ); } } - - // append the new surfaceElement to the segmentParentEl - streetParentEl.append(segmentParentEl); segmentParentEl.setAttribute('position', segmentPositionX + ' 0 0'); segmentParentEl.setAttribute( 'data-layer-name', '' + segments[i].type + ' • ' + variantList[0] ); + segmentEls.push(segmentParentEl); } - - // create new brown box to represent ground underneath street - const dirtBox = document.createElement('a-box'); - const xPos = cumulativeWidthInMeters / 2; - dirtBox.setAttribute('position', `${xPos} -1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 - dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 - dirtBox.setAttribute('width', cumulativeWidthInMeters); - dirtBox.setAttribute('depth', length - 0.2); // depth is length - 0.1 on each side - dirtBox.setAttribute('material', `color: ${STREET.colors.brown};`); - dirtBox.setAttribute('data-layer-name', 'Underground'); - streetParentEl.append(dirtBox); - return streetParentEl; + return segmentEls; } From ba36b779010d47513a4a276d43b866afd643ec82 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 7 Dec 2024 16:17:44 -0800 Subject: [PATCH 085/118] fix justified saving behavior --- src/components/managed-street.js | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 173801f68..c0b79c2b8 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -50,27 +50,32 @@ AFRAME.registerComponent('managed-street', { oneOf: ['center', 'left', 'right'] }, justifyLength: { - default: 'start', + default: 'middle', type: 'string', oneOf: ['middle', 'start', 'end'] } }, init: function () { - this.createdEntities = []; + this.managedEntities = []; this.pendingEntities = []; // Bind the method to preserve context this.refreshFromSource = this.refreshFromSource.bind(this); }, update: function (oldData) { const data = this.data; + const dataDiff = AFRAME.utils.diff(oldData, data); + if (data.synchronize) { this.el.setAttribute('managed-street', 'synchronize', false); this.refreshFromSource(); } + if ( - oldData.justifyWidth !== data.justifyWidth || - oldData.justifyLength !== data.justifyLength + Object.keys(dataDiff).length === 1 && + (Object.keys(dataDiff).includes('justifyWidth') || + Object.keys(dataDiff).includes('justifyLength')) ) { + this.refreshManagedEntities(); this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); } @@ -90,7 +95,7 @@ AFRAME.registerComponent('managed-street', { }, applyJustification: function () { const data = this.data; - const segmentEls = this.createdEntities; + const segmentEls = this.managedEntities; const streetWidth = data.width; const streetLength = data.length; @@ -125,6 +130,14 @@ AFRAME.registerComponent('managed-street', { xPosition += segmentWidth / 2; }); }, + refreshManagedEntities: function () { + // create a list again of the managed entities + this.managedEntities = []; + const segmentEls = this.el.querySelectorAll('[street-segment]'); + segmentEls.forEach((segmentEl) => { + this.managedEntities.push(segmentEl); + }); + }, createOrUpdateJustifiedDirtBox: function () { const data = this.data; const streetWidth = data.width; @@ -133,6 +146,10 @@ AFRAME.registerComponent('managed-street', { } console.log('streetWidth', streetWidth); 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'); @@ -253,11 +270,14 @@ AFRAME.registerComponent('managed-street', { if (index > -1) { this.pendingEntities.splice(index, 1); } - this.createdEntities.push(entity); + this.managedEntities.push(entity); // If no more pending entities, resolve the promise if (this.pendingEntities.length === 0) { this.resolveAllLoaded(); } + }, + remove: function () { + this.managedEntities.forEach((entity) => entity.remove()); } }); From eb35d0628560adc9a18b9ff74cb5e4fd62508f27 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sat, 7 Dec 2024 18:50:41 -0800 Subject: [PATCH 086/118] update length of children from managed-street --- src/components/managed-street.js | 13 +++++++++++++ src/components/street-segment.js | 10 ++++++++++ 2 files changed, 23 insertions(+) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index c0b79c2b8..3f5fbdc6c 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -79,6 +79,10 @@ AFRAME.registerComponent('managed-street', { this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); } + + if (Object.keys(dataDiff).includes('length')) { + this.applyLength(); + } // 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 }, @@ -93,6 +97,15 @@ AFRAME.registerComponent('managed-street', { this.refreshFromJSONBlob(data.sourceValue); } }, + applyLength: function () { + const data = this.data; + const segmentEls = this.managedEntities; + const streetLength = data.length; + + segmentEls.forEach((segmentEl) => { + segmentEl.setAttribute('street-segment', 'length', streetLength); + }); + }, applyJustification: function () { const data = this.data; const segmentEls = this.managedEntities; diff --git a/src/components/street-segment.js b/src/components/street-segment.js index f7a71bd95..fe3b1b3f6 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -281,6 +281,16 @@ AFRAME.registerComponent('street-segment', { this.el.setAttribute(componentName, 'direction', this.data.direction); } } + // propagate change of length to generated components is solo changed + if ( + Object.keys(dataDiff).length === 1 && + Object.keys(dataDiff).includes('length') + ) { + this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import + for (const componentName of this.generatedComponents) { + this.el.setAttribute(componentName, 'length', this.data.length); + } + } this.clearMesh(); this.height = this.calculateHeight(data.level); this.tempXPosition = this.el.getAttribute('position').x; From 22d7fc8359a0972d432d6a5bd2d9dc8e90868a1f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 8 Dec 2024 11:13:04 -0800 Subject: [PATCH 087/118] managed street custom sidebar --- src/components/managed-street.js | 4 +- .../components/ManagedStreetSidebar.js | 50 +++++++++++++++++++ src/editor/components/components/Sidebar.js | 27 ++++++++-- src/editor/icons/icons.jsx | 31 +++++++++++- src/editor/icons/index.js | 3 +- 5 files changed, 108 insertions(+), 7 deletions(-) create mode 100644 src/editor/components/components/ManagedStreetSidebar.js diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 3f5fbdc6c..9ed9cbb8d 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -19,8 +19,8 @@ AFRAME.registerComponent('managed-street', { type: 'number' }, length: { - type: 'string', - default: '60' + type: 'number', + default: 60 }, sourceType: { type: 'string', diff --git a/src/editor/components/components/ManagedStreetSidebar.js b/src/editor/components/components/ManagedStreetSidebar.js new file mode 100644 index 000000000..199f60bcc --- /dev/null +++ b/src/editor/components/components/ManagedStreetSidebar.js @@ -0,0 +1,50 @@ +import PropTypes from 'prop-types'; +import PropertyRow from './PropertyRow'; + +const ManagedStreetSidebar = ({ entity }) => { + const componentName = 'managed-street'; + // Check if entity and its components exist + const component = entity?.components?.[componentName]; + + return ( +
+
+
+ {component && component.schema && component.data && ( + <> + + +
+
-----
+
+ + )} +
+
+
+ ); +}; + +ManagedStreetSidebar.propTypes = { + entity: PropTypes.object.isRequired +}; + +export default ManagedStreetSidebar; diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index e73fdbb2f..640dbdd81 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -7,10 +7,16 @@ import PropTypes from 'prop-types'; import React from 'react'; import capitalize from 'lodash-es/capitalize'; import classnames from 'classnames'; -import { ArrowRightIcon, Object24Icon, SegmentIcon } from '../../icons'; +import { + ArrowRightIcon, + Object24Icon, + SegmentIcon, + ManagedStreetIcon +} from '../../icons'; import GeoSidebar from './GeoSidebar'; // Make sure to create and import this new component import IntersectionSidebar from './IntersectionSidebar'; import StreetSegmentSidebar from './StreetSegmentSidebar'; +import ManagedStreetSidebar from './ManagedStreetSidebar'; import AdvancedComponents from './AdvancedComponents'; export default class Sidebar extends React.Component { @@ -84,6 +90,17 @@ export default class Sidebar extends React.Component { ); } + if (entity.getAttribute('managed-street')) { + return ( + <> + +
+ +
+ + ); + } + const isIntersection = entity.getAttribute('intersection'); const hasNoTransform = entity.hasAttribute('data-no-transform'); @@ -117,7 +134,9 @@ export default class Sidebar extends React.Component {
{displayName}
- {entity.getAttribute('street-segment') ? ( + {entity.getAttribute('managed-street') ? ( + + ) : entity.getAttribute('street-segment') ? ( ) : ( @@ -154,7 +173,9 @@ export default class Sidebar extends React.Component { <>
- {entity.getAttribute('street-segment') ? ( + {entity.getAttribute('managed-street') ? ( + + ) : entity.getAttribute('street-segment') ? ( ) : ( diff --git a/src/editor/icons/icons.jsx b/src/editor/icons/icons.jsx index ff9a8f634..23ec836d6 100644 --- a/src/editor/icons/icons.jsx +++ b/src/editor/icons/icons.jsx @@ -757,6 +757,34 @@ const SignInMicrosoftIconSVG = () => ( ); +const ManagedStreetIcon = () => ( + + + + + + + + +); const SegmentIcon = () => ( Date: Sun, 8 Dec 2024 11:19:59 -0800 Subject: [PATCH 088/118] trigger reflow when segment width changed --- src/components/street-segment.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index fe3b1b3f6..5d6cc6486 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -296,6 +296,10 @@ AFRAME.registerComponent('street-segment', { this.tempXPosition = this.el.getAttribute('position').x; this.el.setAttribute('position', { x: this.tempXPosition, y: this.height }); this.generateMesh(data); + // if width was changed, trigger re-justification of all street-segments by the managed-street + if (Object.keys(dataDiff).includes('width')) { + this.el.parentNode.components['managed-street'].applyJustification(); + } }, // for streetmix elevation number values of -1, 0, 1, 2, calculate heightLevel in three.js meters units calculateHeight: function (elevationLevel) { From 4c183909ecf92843cd4d22d9a8b2f1ee5ecc23b7 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 12:06:57 -0800 Subject: [PATCH 089/118] self not needed --- src/components/managed-street.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 9ed9cbb8d..1e4deb490 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -87,8 +87,7 @@ AFRAME.registerComponent('managed-street', { // we need to get a list of all the child objects whose length we need to change }, refreshFromSource: function () { - const self = this; - const data = self.data; + const data = this.data; if (data.sourceType === 'streetmix-url') { this.loadAndParseStreetmixURL(data.sourceValue); } else if (data.sourceType === 'streetplan-url') { From 3378a6a1651b2db41473f828beef5878d1d423b4 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 12:07:55 -0800 Subject: [PATCH 090/118] no log --- src/components/managed-street.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 1e4deb490..4c860807c 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -156,7 +156,6 @@ AFRAME.registerComponent('managed-street', { if (!streetWidth) { return; } - console.log('streetWidth', streetWidth); const streetLength = data.length; if (!this.justifiedDirtBox) { // try to find an existing dirt box From 6359eabc6d3c944ee9315fc21a34af07a66d4190 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 13:24:49 -0800 Subject: [PATCH 091/118] better array clearing - don't create multiple Object.keys - don't need to check deepEqual old vs new data --- src/components/managed-street.js | 13 +++++++------ src/components/street-generated-clones.js | 7 ++----- src/components/street-generated-pedestrians.js | 4 ++-- src/components/street-generated-rail.js | 6 ++---- src/components/street-generated-stencil.js | 4 ++-- src/components/street-generated-striping.js | 4 ++-- 6 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 4c860807c..0d62b8521 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -70,17 +70,18 @@ AFRAME.registerComponent('managed-street', { this.refreshFromSource(); } + const dataDiffKeys = Object.keys(dataDiff); if ( - Object.keys(dataDiff).length === 1 && - (Object.keys(dataDiff).includes('justifyWidth') || - Object.keys(dataDiff).includes('justifyLength')) + dataDiffKeys.length === 1 && + (dataDiffKeys.includes('justifyWidth') || + dataDiffKeys.includes('justifyLength')) ) { this.refreshManagedEntities(); this.applyJustification(); this.createOrUpdateJustifiedDirtBox(); } - if (Object.keys(dataDiff).includes('length')) { + if (dataDiffKeys.includes('length')) { this.applyLength(); } // if the value of length changes, then we need to update the length of all the child objects @@ -390,13 +391,13 @@ function getRandomIntInclusive(min, max) { } function getSegmentColor(variant) { - if ((variant === 'red') | (variant === 'colored')) { + if (variant === 'red' || variant === 'colored') { return window.STREET.colors.red; } if (variant === 'blue') { return window.STREET.colors.blue; } - if ((variant === 'green') | (variant === 'grass')) { + if (variant === 'green' || variant === 'grass') { return window.STREET.colors.green; } return window.STREET.colors.white; diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index ad232d11d..473ba7071 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -36,18 +36,15 @@ AFRAME.registerComponent('street-generated-clones', { remove: function () { this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities.length = 0; // Clear the array }, update: function (oldData) { const data = this.data; - if (AFRAME.utils.deepEqual(oldData, data)) { - return; - } - // Clear existing entities this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities = []; + this.createdEntities.length = 0; // Clear the array // Generate new entities based on mode switch (data.mode) { diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js index 6c73f6ceb..82a3fa5af 100644 --- a/src/components/street-generated-pedestrians.js +++ b/src/components/street-generated-pedestrians.js @@ -47,15 +47,15 @@ AFRAME.registerComponent('street-generated-pedestrians', { remove: function () { this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities.length = 0; // Clear the array }, update: function (oldData) { const data = this.data; - if (AFRAME.utils.deepEqual(oldData, data)) return; // Clean up old entities this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities = []; + this.createdEntities.length = 0; // Clear the array // Calculate x position range based on segment width const xRange = { diff --git a/src/components/street-generated-rail.js b/src/components/street-generated-rail.js index 69f18df21..30e15abc8 100644 --- a/src/components/street-generated-rail.js +++ b/src/components/street-generated-rail.js @@ -18,14 +18,12 @@ AFRAME.registerComponent('street-generated-rail', { }, remove: function () { this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities.length = 0; // Clear the array }, update: function (oldData) { - const data = this.data; - if (AFRAME.utils.deepEqual(oldData, data)) return; - // Clean up old entities this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities = []; + this.createdEntities.length = 0; // Clear the array const clone = document.createElement('a-entity'); clone.setAttribute('data-layer-name', 'Cloned Railroad Tracks'); diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 5eac38c1b..98ff60913 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -97,14 +97,14 @@ AFRAME.registerComponent('street-generated-stencil', { }, remove: function () { this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities.length = 0; // Clear the array }, update: function (oldData) { const data = this.data; - if (AFRAME.utils.deepEqual(oldData, data)) return; // Clean up old entities this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities = []; + this.createdEntities.length = 0; // Clear the array // Use either stencils array or single model let stencilsToUse = data.stencils.length > 0 ? data.stencils : [data.model]; diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 708a40699..55342856b 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -35,14 +35,14 @@ AFRAME.registerComponent('street-generated-striping', { }, remove: function () { this.createdEntities.forEach((entity) => entity.remove()); + this.createdEntities.length = 0; // Clear the array }, update: function (oldData) { const data = this.data; - if (AFRAME.utils.deepEqual(oldData, data)) return; // Clean up old entities this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities = []; + this.createdEntities.length = 0; // Clear the array if (data.striping === 'invisible') { return; } From 66b5ef49df232f150ca4ceec6f8f57a94a180fe9 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:06:46 -0800 Subject: [PATCH 092/118] update length when changed --- src/components/managed-street.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 0d62b8521..9233c2a1e 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -82,6 +82,7 @@ AFRAME.registerComponent('managed-street', { } if (dataDiffKeys.includes('length')) { + this.refreshManagedEntities(); this.applyLength(); } // if the value of length changes, then we need to update the length of all the child objects @@ -290,6 +291,7 @@ AFRAME.registerComponent('managed-street', { }, remove: function () { this.managedEntities.forEach((entity) => entity.remove()); + this.managedEntities.length = 0; // Clear the array } }); From ce004b0d8c51cf4598a2a2dcaf3f13e39ba596be Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:07:06 -0800 Subject: [PATCH 093/118] use remove() instead of duplicate code --- src/components/street-generated-clones.js | 7 ++----- src/components/street-generated-pedestrians.js | 3 +-- src/components/street-generated-rail.js | 3 +-- src/components/street-generated-stencil.js | 3 +-- src/components/street-generated-striping.js | 4 ++-- 5 files changed, 7 insertions(+), 13 deletions(-) diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index 473ba7071..2c7148626 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -40,14 +40,11 @@ AFRAME.registerComponent('street-generated-clones', { }, update: function (oldData) { - const data = this.data; - // Clear existing entities - this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities.length = 0; // Clear the array + this.remove(); // Generate new entities based on mode - switch (data.mode) { + switch (this.data.mode) { case 'fixed': this.generateFixed(); break; diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js index 82a3fa5af..2f3e2acca 100644 --- a/src/components/street-generated-pedestrians.js +++ b/src/components/street-generated-pedestrians.js @@ -54,8 +54,7 @@ AFRAME.registerComponent('street-generated-pedestrians', { const data = this.data; // Clean up old entities - this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities.length = 0; // Clear the array + this.remove(); // Calculate x position range based on segment width const xRange = { diff --git a/src/components/street-generated-rail.js b/src/components/street-generated-rail.js index 30e15abc8..f65092179 100644 --- a/src/components/street-generated-rail.js +++ b/src/components/street-generated-rail.js @@ -22,8 +22,7 @@ AFRAME.registerComponent('street-generated-rail', { }, update: function (oldData) { // Clean up old entities - this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities.length = 0; // Clear the array + this.remove(); const clone = document.createElement('a-entity'); clone.setAttribute('data-layer-name', 'Cloned Railroad Tracks'); diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 98ff60913..63736d244 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -103,8 +103,7 @@ AFRAME.registerComponent('street-generated-stencil', { const data = this.data; // Clean up old entities - this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities.length = 0; // Clear the array + this.remove(); // Use either stencils array or single model let stencilsToUse = data.stencils.length > 0 ? data.stencils : [data.model]; diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 55342856b..395ca98c6 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -41,8 +41,8 @@ AFRAME.registerComponent('street-generated-striping', { const data = this.data; // Clean up old entities - this.createdEntities.forEach((entity) => entity.remove()); - this.createdEntities.length = 0; // Clear the array + this.remove(); + if (data.striping === 'invisible') { return; } From b61593556baf55ea3731463fc84f798148446583 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:16:14 -0800 Subject: [PATCH 094/118] small fixes - don't need length === 1 for direction and length (keep for type) - create var for dataDiff keys (changedProps) --- src/components/street-segment.js | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 5d6cc6486..62f357890 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -256,15 +256,10 @@ AFRAME.registerComponent('street-segment', { update: function (oldData) { const data = this.data; const dataDiff = AFRAME.utils.diff(oldData, data); - // if oldData is same as current data, then don't update - if (AFRAME.utils.deepEqual(oldData, data)) { - return; - } + const changedProps = Object.keys(dataDiff); + // regenerate components if only type has changed - if ( - Object.keys(dataDiff).length === 1 && - Object.keys(dataDiff).includes('type') - ) { + if (changedProps.length === 1 && changedProps.includes('type')) { let typeObject = this.types[this.data.type]; this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import this.remove(); @@ -272,20 +267,14 @@ AFRAME.registerComponent('street-segment', { this.updateSurfaceFromType(typeObject); // update surface color, surface, level } // propagate change of direction to generated components is solo changed - if ( - Object.keys(dataDiff).length === 1 && - Object.keys(dataDiff).includes('direction') - ) { + if (changedProps.includes('direction')) { this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import for (const componentName of this.generatedComponents) { this.el.setAttribute(componentName, 'direction', this.data.direction); } } // propagate change of length to generated components is solo changed - if ( - Object.keys(dataDiff).length === 1 && - Object.keys(dataDiff).includes('length') - ) { + if (changedProps.includes('length')) { this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import for (const componentName of this.generatedComponents) { this.el.setAttribute(componentName, 'length', this.data.length); @@ -297,7 +286,7 @@ AFRAME.registerComponent('street-segment', { this.el.setAttribute('position', { x: this.tempXPosition, y: this.height }); this.generateMesh(data); // if width was changed, trigger re-justification of all street-segments by the managed-street - if (Object.keys(dataDiff).includes('width')) { + if (changedProps.includes('width')) { this.el.parentNode.components['managed-street'].applyJustification(); } }, From 0220b81bf1a7de20ca0d694d7bf497187848f072 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:18:03 -0800 Subject: [PATCH 095/118] adding more array clearing --- src/components/street-segment.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 62f357890..342adf8fc 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -309,6 +309,7 @@ AFRAME.registerComponent('street-segment', { this.generatedComponents.forEach((componentName) => { this.el.removeAttribute(componentName); }); + this.generatedComponents.length = 0; }, generateMesh: function (data) { // create geometry From bdcc04f89b35d76d0349b4da0ca4c4b52365ba7f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:27:41 -0800 Subject: [PATCH 096/118] simplify icon code --- src/editor/icons/icons.jsx | 112 ++++++++++++------------------------- src/editor/icons/index.js | 39 +------------ 2 files changed, 38 insertions(+), 113 deletions(-) diff --git a/src/editor/icons/icons.jsx b/src/editor/icons/icons.jsx index 23ec836d6..e5559ae24 100644 --- a/src/editor/icons/icons.jsx +++ b/src/editor/icons/icons.jsx @@ -1,4 +1,4 @@ -const Camera32Icon = () => ( +export const Camera32Icon = () => ( ( ); -const Rotate24Icon = () => ( +export const Rotate24Icon = () => ( ( ); -const Translate24Icon = () => ( +export const Translate24Icon = () => ( ( ); -const Cloud24Icon = () => ( +export const Cloud24Icon = () => ( ( ); -const Load24Icon = () => ( +export const Load24Icon = () => ( ( ); -const Save24Icon = () => ( +export const Save24Icon = () => ( ( ); -const Upload24Icon = () => ( +export const Upload24Icon = () => ( ( ); -const Cross32Icon = () => ( +export const Cross32Icon = () => ( ( ); -const Cross24Icon = () => ( +export const Cross24Icon = () => ( ( ); -const Compass32Icon = () => ( +export const Compass32Icon = () => ( ( ); -const ArrowDown24Icon = () => ( +export const ArrowDown24Icon = () => ( ( ); -const ArrowUp24Icon = () => ( +export const ArrowUp24Icon = () => ( ( ); -const CheckIcon = (className) => ( +export const CheckIcon = (className) => ( ( ); -const DropdownArrowIcon = () => ( +export const DropdownArrowIcon = () => ( ( ); -const Mangnifier20Icon = () => ( +export const Mangnifier20Icon = () => ( ( ); -const Edit32Icon = () => ( +export const Edit32Icon = () => ( ( ); -const Edit24Icon = () => ( +export const Edit24Icon = () => ( ( ); -const CheckMark32Icon = () => ( +export const CheckMark32Icon = () => ( ( ); -const Action24 = () => ( +export const Action24 = () => ( ( ); -const DownloadIcon = () => ( +export const DownloadIcon = () => ( ( ); -const Copy32Icon = ({ className }) => ( +export const Copy32Icon = ({ className }) => ( ( ); -const DropdownIcon = () => ( +export const DropdownIcon = () => ( ( ); -const Loader = ({ className }) => ( +export const Loader = ({ className }) => ( ( ); -const RemixIcon = ({ className }) => ( +export const RemixIcon = ({ className }) => ( ( ); -const ArrowLeftIcon = ({ className }) => ( +export const ArrowLeftIcon = ({ className }) => ( ( ); -const ArrowRightIcon = ({ className }) => ( +export const ArrowRightIcon = ({ className }) => ( ( ); -const LayersIcon = ({ className }) => ( +export const LayersIcon = ({ className }) => ( ( ); -const GoogleSignInButtonSVG = ({ className }) => ( +export const GoogleSignInButtonSVG = ({ className }) => ( ( ); -const Chevron24Down = ({ className }) => ( +export const Chevron24Down = ({ className }) => ( ( ); -const Plus20Circle = ({ className }) => ( +export const Plus20Circle = ({ className }) => ( ( ); -const QR32Icon = () => ( +export const QR32Icon = () => ( ( ); -const ScreenshotIcon = () => ( +export const ScreenshotIcon = () => ( ( ); -const Object24Icon = () => ( +export const Object24Icon = () => ( ( ); -const SignInMicrosoftIconSVG = () => ( +export const SignInMicrosoftIconSVG = () => ( ( ); -const ManagedStreetIcon = () => ( +export const ManagedStreetIcon = () => ( ( ); -const SegmentIcon = () => ( + +export const SegmentIcon = () => ( ( /> ); - -export { - Camera32Icon, - Save24Icon, - Load24Icon, - Cross32Icon, - Cross24Icon, - Compass32Icon, - ArrowDown24Icon, - ArrowUp24Icon, - CheckIcon, - DropdownArrowIcon, - Cloud24Icon, - Mangnifier20Icon, - Upload24Icon, - Edit32Icon, - Edit24Icon, - CheckMark32Icon, - Copy32Icon, - DropdownIcon, - Loader, - RemixIcon, - ArrowLeftIcon, - ArrowRightIcon, - LayersIcon, - GoogleSignInButtonSVG, - Chevron24Down, - Plus20Circle, - QR32Icon, - ScreenshotIcon, - DownloadIcon, - Action24, - SignInMicrosoftIconSVG, - Rotate24Icon, - Translate24Icon, - Object24Icon, - SegmentIcon, - ManagedStreetIcon -}; diff --git a/src/editor/icons/index.js b/src/editor/icons/index.js index deb6fd55d..58b9bf38e 100644 --- a/src/editor/icons/index.js +++ b/src/editor/icons/index.js @@ -1,38 +1 @@ -export { - Camera32Icon, - Save24Icon, - Load24Icon, - Upload24Icon, - Cross32Icon, - Cross24Icon, - Compass32Icon, - ArrowDown24Icon, - ArrowUp24Icon, - CheckIcon, - DropdownArrowIcon, - Cloud24Icon, - Mangnifier20Icon, - Edit32Icon, - Edit24Icon, - CheckMark32Icon, - Copy32Icon, - DropdownIcon, - Loader, - RemixIcon, - ArrowLeftIcon, - ArrowRightIcon, - LayersIcon, - GoogleSignInButtonSVG, - Chevron24Down, - Plus20Circle, - QR32Icon, - Action24, - DownloadIcon, - ScreenshotIcon, - SignInMicrosoftIconSVG, - Rotate24Icon, - Translate24Icon, - Object24Icon, - SegmentIcon, - ManagedStreetIcon -} from './icons.jsx'; +export * from './icons.jsx'; From fcb5dcb438e67ad4cff2f86d8c567f23a00905f0 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:33:12 -0800 Subject: [PATCH 097/118] update z as well --- src/components/street-segment.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 342adf8fc..699e6c6b9 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -283,7 +283,12 @@ AFRAME.registerComponent('street-segment', { this.clearMesh(); this.height = this.calculateHeight(data.level); this.tempXPosition = this.el.getAttribute('position').x; - this.el.setAttribute('position', { x: this.tempXPosition, y: this.height }); + this.tempZPosition = this.el.getAttribute('position').z; + this.el.setAttribute('position', { + x: this.tempXPosition, + y: this.height, + z: this.tempZPosition + }); this.generateMesh(data); // if width was changed, trigger re-justification of all street-segments by the managed-street if (changedProps.includes('width')) { From b29389cd25285d92972b191f4be1814ea68b4ec2 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:38:54 -0800 Subject: [PATCH 098/118] ensure autocreated rails aren't saved --- src/components/street-generated-rail.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/components/street-generated-rail.js b/src/components/street-generated-rail.js index f65092179..994a707b5 100644 --- a/src/components/street-generated-rail.js +++ b/src/components/street-generated-rail.js @@ -32,6 +32,7 @@ AFRAME.registerComponent('street-generated-rail', { clone.append(this.createRailsElement(this.data.length, -railsPosX)); clone.setAttribute('data-no-transform', ''); clone.setAttribute('data-ignore-raycaster', ''); + clone.classList.add('autocreated'); this.el.appendChild(clone); this.createdEntities.push(clone); @@ -58,6 +59,8 @@ AFRAME.registerComponent('street-generated-rail', { placedObjectEl.setAttribute('data-no-transform', ''); placedObjectEl.setAttribute('data-ignore-raycaster', ''); placedObjectEl.setAttribute('position', railsPosX + ' 0.2 0'); // position="1.043 0.100 -3.463" + placedObjectEl.classList.add('autocreated'); + this.createdEntities.push(placedObjectEl); return placedObjectEl; } From ceff9f060fef8cb9a63903c4bec73829289326a0 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:39:39 -0800 Subject: [PATCH 099/118] check node exists before remove --- src/components/managed-street.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 9233c2a1e..ff674b78f 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -290,7 +290,9 @@ AFRAME.registerComponent('managed-street', { } }, remove: function () { - this.managedEntities.forEach((entity) => entity.remove()); + this.managedEntities.forEach( + (entity) => entity.parentNode && entity.remove() + ); this.managedEntities.length = 0; // Clear the array } }); From e4a0cc2d553ce176fd412a851a777a7d4e9f9021 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:42:10 -0800 Subject: [PATCH 100/118] fix atlas-uv refresh hack --- src/components/street-generated-stencil.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 63736d244..539455854 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -145,7 +145,7 @@ AFRAME.registerComponent('street-generated-stencil', { if (data.stencilHeight > 0) { clone.addEventListener('loaded', (evt) => { evt.target.setAttribute('geometry', 'height', data.stencilHeight); - evt.target.setAttribute('atlas-uvs', 'forceRefresh', true); + evt.target.components['atlas-uvs'].update(); }); } From d2ac2b8c8a48f4927d87328164364c440725c42f Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 14:49:03 -0800 Subject: [PATCH 101/118] simplify managedEntities refresh --- src/components/managed-street.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index ff674b78f..aba59f380 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -146,11 +146,9 @@ AFRAME.registerComponent('managed-street', { }, refreshManagedEntities: function () { // create a list again of the managed entities - this.managedEntities = []; - const segmentEls = this.el.querySelectorAll('[street-segment]'); - segmentEls.forEach((segmentEl) => { - this.managedEntities.push(segmentEl); - }); + this.managedEntities = Array.from( + this.el.querySelectorAll('[street-segment]') + ); }, createOrUpdateJustifiedDirtBox: function () { const data = this.data; From f815569325cf340750d0c34550cf9fb264d29c9e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 15:25:20 -0800 Subject: [PATCH 102/118] remove existing segments upon synchronize --- src/components/managed-street.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index aba59f380..828e10551 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -218,6 +218,8 @@ AFRAME.registerComponent('managed-street', { request.onload = function () { if (this.status >= 200 && this.status < 400) { // Connection success + self.refreshManagedEntities(); + self.remove(); const streetmixResponseObject = JSON.parse(this.response); // convert units of measurement if necessary const streetData = streetmixUtils.convertStreetValues( From 1a60d8788c58027dbfe8419633be3d97f58ce81e Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 15:32:18 -0800 Subject: [PATCH 103/118] update sidebar after managed street load --- src/components/managed-street.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 828e10551..a4dc99ff9 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -259,6 +259,7 @@ AFRAME.registerComponent('managed-street', { self.allLoadedPromise.then(() => { self.applyJustification(); self.createOrUpdateJustifiedDirtBox(); + AFRAME.INSPECTOR.selectEntity(self.el); }); } else { // We reached our target server, but it returned an error From a5fa2952a45e7986003b2dd7a4639ffbf7dcc646 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Wed, 11 Dec 2024 15:36:32 -0800 Subject: [PATCH 104/118] move managed street to addlayer street group --- .../components/AddLayerPanel/layersData.js | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/editor/components/components/AddLayerPanel/layersData.js b/src/editor/components/components/AddLayerPanel/layersData.js index 753f09e62..42278192d 100644 --- a/src/editor/components/components/AddLayerPanel/layersData.js +++ b/src/editor/components/components/AddLayerPanel/layersData.js @@ -72,6 +72,16 @@ export const streetLayersData = [ 'Create intersection entity. Parameters of intersection component could be changed in properties panel.', id: 7, handlerFunction: createIntersection + }, + { + name: 'Create Managed Street (Beta)', + img: '', + requiresPro: true, + icon: '', + description: + 'Create a new street from Streetmix using the Managed Street component.', + id: 8, + handlerFunction: createManagedStreet } ]; @@ -115,15 +125,5 @@ export const customLayersData = [ 'Place an image such as a sign, reference photo, custom map, etc.', id: 4, handlerFunction: createImageEntity - }, - { - name: 'Create Managed Street (Beta)', - img: '', - requiresPro: true, - icon: '', - description: - 'Create a new street from Streetmix using the Managed Street component.', - id: 5, - handlerFunction: createManagedStreet } ]; From f37c00bd400fe05417cd02c1564bf8e65a021af3 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 12 Dec 2024 10:13:57 +0100 Subject: [PATCH 105/118] use || operator, not | --- src/aframe-streetmix-parsers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 2ac9f62ae..cdcf9b963 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -352,7 +352,7 @@ function getBikeLaneMixin(variant) { } function getBusLaneMixin(variant) { - if ((variant === 'colored') | (variant === 'red')) { + if (variant === 'colored' || variant === 'red') { return 'surface-red bus-lane'; } if (variant === 'blue') { From bace2cb1157fb5111d8f7cf9815733607e0d2cb2 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 12 Dec 2024 10:38:21 +0100 Subject: [PATCH 106/118] update dirtBox width and depth when changing managed-street width and length --- src/components/managed-street.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index a4dc99ff9..852dc3572 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -81,9 +81,14 @@ AFRAME.registerComponent('managed-street', { this.createOrUpdateJustifiedDirtBox(); } + 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 @@ -164,16 +169,15 @@ AFRAME.registerComponent('managed-street', { if (!this.justifiedDirtBox) { // create new brown box to represent ground underneath street const dirtBox = document.createElement('a-box'); - // dirtBox.setAttribute('position', `${xPosition} -1 0`); // what is x? x = 0 - cumulativeWidthInMeters / 2 - dirtBox.setAttribute('height', 2); // height is 2 meters from y of -0.1 to -y of 2.1 - dirtBox.setAttribute('width', streetWidth); - dirtBox.setAttribute('depth', streetLength - 0.2); // depth is length - 0.1 on each side - dirtBox.setAttribute('material', `color: ${window.STREET.colors.brown};`); - dirtBox.setAttribute('data-layer-name', 'Underground'); 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'); } + 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('depth', streetLength - 0.2); // depth is length - 0.1 on each side // set starting xPosition for width justification let xPosition = 0; // default for center justified From 6e8f820859f91d0fce48b4dadba043a11eba7541 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 12 Dec 2024 10:49:51 +0100 Subject: [PATCH 107/118] use const/let instead of var in new code --- src/components/managed-street.js | 33 +++++++++++---------- src/components/street-generated-clones.js | 2 +- src/components/street-generated-stencil.js | 2 +- src/components/street-generated-striping.js | 8 ++--- 4 files changed, 23 insertions(+), 22 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 852dc3572..7580cbaa8 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -1,9 +1,9 @@ /* global AFRAME */ // Orientation - default model orientation is "outbound" (away from camera) -var { segmentVariants } = require('../segments-variants.js'); -var streetmixUtils = require('../tested/streetmix-utils'); -var streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested'); +const { segmentVariants } = require('../segments-variants.js'); +const streetmixUtils = require('../tested/streetmix-utils'); +const streetmixParsersTested = require('../tested/aframe-streetmix-parsers-tested'); // invoking from js console /* @@ -438,22 +438,23 @@ function supportCheck(segmentType, segmentVariantString) { // NEW: takes a `segments` (array) from streetmix and return an element and its children which represent the 3D street scene function parseStreetmixSegments(segments, showStriping, length, showVehicles) { // create and center offset to center the street around global x position of 0 - var segmentEls = []; + const segmentEls = []; - var cumulativeWidthInMeters = 0; - for (var i = 0; i < segments.length; i++) { - var segmentColor = null; - var segmentParentEl = document.createElement('a-entity'); + let cumulativeWidthInMeters = 0; + for (let i = 0; i < segments.length; i++) { + let segmentColor = null; + const segmentParentEl = document.createElement('a-entity'); segmentParentEl.classList.add('segment-parent-' + i); - var segmentWidthInMeters = segments[i].width; + const segmentWidthInMeters = segments[i].width; // console.log('Type: ' + segments[i].type + '; Width: ' + segmentWidthInFeet + 'ft / ' + segmentWidthInMeters + 'm'); cumulativeWidthInMeters = cumulativeWidthInMeters + segmentWidthInMeters; - var segmentPositionX = cumulativeWidthInMeters - 0.5 * segmentWidthInMeters; + const segmentPositionX = + cumulativeWidthInMeters - 0.5 * segmentWidthInMeters; // get variantString - var variantList = segments[i].variantString + const variantList = segments[i].variantString ? segments[i].variantString.split('|') : ''; @@ -463,13 +464,13 @@ function parseStreetmixSegments(segments, showStriping, length, showVehicles) { // elevation property from streetmix segment const elevation = segments[i].elevation; - var direction = + const direction = variantList[0] === 'inbound' || variantList[1] === 'inbound' ? 'inbound' : 'outbound'; // the A-Frame mixin ID is often identical to the corresponding streetmix segment "type" by design, let's start with that - var segmentPreset = segments[i].type; + let segmentPreset = segments[i].type; // look at segment type and variant(s) to determine specific cases if (segments[i].type === 'drive-lane' && variantList[1] === 'sharrow') { @@ -530,7 +531,7 @@ function parseStreetmixSegments(segments, showStriping, length, showVehicles) { count: ${getRandomIntInclusive(2, 4)};` ); } - var markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name + let markerMixinId = variantList[1]; // set the mixin of the road markings to match the current variant name // Fix streetmix inbound turn lane orientation (change left to right) per: https://github.com/streetmix/streetmix/issues/683 if (variantList[0] === 'inbound') { markerMixinId = markerMixinId.replace(/left|right/g, function (m) { @@ -788,7 +789,7 @@ function parseStreetmixSegments(segments, showStriping, length, showVehicles) { `model: lamp-modern-double; length: ${length}; cycleOffset: 0.4;` ); } else { - var rotationCloneY = variantList[0] === 'right' ? 0 : 180; + const rotationCloneY = variantList[0] === 'right' ? 0 : 180; segmentParentEl.setAttribute( 'street-generated-clones', `model: lamp-modern; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.4;` @@ -822,7 +823,7 @@ function parseStreetmixSegments(segments, showStriping, length, showVehicles) { `model: lamp-traditional; length: ${length};` ); } else if (segments[i].type === 'transit-shelter') { - var rotationBusStopY = variantList[0] === 'left' ? 90 : 270; + const rotationBusStopY = variantList[0] === 'left' ? 90 : 270; segmentParentEl.setAttribute( 'street-generated-clones', `mode: single; model: bus-stop; length: ${length}; facing: ${rotationBusStopY};` diff --git a/src/components/street-generated-clones.js b/src/components/street-generated-clones.js index 2c7148626..79d93e94d 100644 --- a/src/components/street-generated-clones.js +++ b/src/components/street-generated-clones.js @@ -107,7 +107,7 @@ AFRAME.registerComponent('street-generated-clones', { z: positionZ }); - var rotationY = data.facing; + let rotationY = data.facing; if (data.direction === 'inbound') { rotationY = 0 + data.facing; } diff --git a/src/components/street-generated-stencil.js b/src/components/street-generated-stencil.js index 539455854..b78e33969 100644 --- a/src/components/street-generated-stencil.js +++ b/src/components/street-generated-stencil.js @@ -150,7 +150,7 @@ AFRAME.registerComponent('street-generated-stencil', { } // Set rotation - either random, specified facing, or inbound/outbound - var rotationY = data.facing; + let rotationY = data.facing; if (data.direction === 'inbound') { rotationY = 180 + data.facing; } diff --git a/src/components/street-generated-striping.js b/src/components/street-generated-striping.js index 395ca98c6..8714eb722 100644 --- a/src/components/street-generated-striping.js +++ b/src/components/street-generated-striping.js @@ -80,10 +80,10 @@ AFRAME.registerComponent('street-generated-striping', { }, calculateStripingMaterial: function (stripingName, length) { // calculate the repeatCount for the material - var stripingTextureId = 'striping-solid-stripe'; // drive-lane, bus-lane, bike-lane - var repeatY = length / 6; - var color = '#ffffff'; - var stripingWidth = 0.2; + let stripingTextureId = 'striping-solid-stripe'; // drive-lane, bus-lane, bike-lane + let repeatY = length / 6; + let color = '#ffffff'; + let stripingWidth = 0.2; if (stripingName === 'solid-stripe') { stripingTextureId = 'striping-solid-stripe'; } else if (stripingName === 'dashed-stripe') { From 7ede26d86aadf563b8ca4e8b39d8a0d88179a7d9 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 12 Dec 2024 10:52:18 +0100 Subject: [PATCH 108/118] register loaded listener with once: true --- src/components/managed-street.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 7580cbaa8..22a9722ed 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -251,9 +251,13 @@ AFRAME.registerComponent('managed-street', { self.pendingEntities = segmentEls; // for each pending entity Listen for loaded event for (const entity of self.pendingEntities) { - entity.addEventListener('loaded', () => { - self.onEntityLoaded(entity); - }); + entity.addEventListener( + 'loaded', + () => { + self.onEntityLoaded(entity); + }, + { once: true } + ); } // Set up a promise that resolves when all entities are loaded self.allLoadedPromise = new Promise((resolve) => { From a93281354208f0abdf5a3a64810356c5cff93982 Mon Sep 17 00:00:00 2001 From: Vincent Fretin Date: Thu, 12 Dec 2024 11:21:16 +0100 Subject: [PATCH 109/118] emit entityselect even if entity is null so the React UI updates properly --- src/editor/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/editor/index.js b/src/editor/index.js index 601187700..a91149ff9 100644 --- a/src/editor/index.js +++ b/src/editor/index.js @@ -153,7 +153,7 @@ Inspector.prototype = { this.select(null); } - if (entity && emit === undefined) { + if (emit === undefined) { Events.emit('entityselect', entity); } From 98eccf4da87938cafaab79d087f0248653e07544 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Thu, 12 Dec 2024 14:38:35 -0800 Subject: [PATCH 110/118] use fetch instead of xmlhttprequest --- src/components/managed-street.js | 121 ++++++++++++++----------------- 1 file changed, 54 insertions(+), 67 deletions(-) diff --git a/src/components/managed-street.js b/src/components/managed-street.js index 22a9722ed..d70cdabc1 100644 --- a/src/components/managed-street.js +++ b/src/components/managed-street.js @@ -202,7 +202,7 @@ AFRAME.registerComponent('managed-street', { `${xPosition} -1 ${zPosition}` ); }, - loadAndParseStreetmixURL: function (streetmixURL) { + loadAndParseStreetmixURL: async function (streetmixURL) { const data = this.data; const streetmixAPIURL = streetmixUtils.streetmixUserToAPI(streetmixURL); console.log( @@ -211,80 +211,67 @@ AFRAME.registerComponent('managed-street', { streetmixAPIURL ); - const request = new XMLHttpRequest(); - console.log('[managed-street] loader', 'GET ' + streetmixAPIURL); + try { + console.log('[managed-street] loader', 'GET ' + streetmixAPIURL); + const response = await fetch(streetmixAPIURL); - request.open('GET', streetmixAPIURL, true); + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } - // Bind the component instance to use inside the callbacks - const self = this; + const streetmixResponseObject = await response.json(); + this.refreshManagedEntities(); + this.remove(); - request.onload = function () { - if (this.status >= 200 && this.status < 400) { - // Connection success - self.refreshManagedEntities(); - self.remove(); - const streetmixResponseObject = JSON.parse(this.response); - // convert units of measurement if necessary - const streetData = streetmixUtils.convertStreetValues( - streetmixResponseObject.data.street - ); - const streetmixSegments = streetData.segments; + // convert units of measurement if necessary + const streetData = streetmixUtils.convertStreetValues( + streetmixResponseObject.data.street + ); + const streetmixSegments = streetData.segments; - const streetmixName = streetmixResponseObject.name; + const streetmixName = streetmixResponseObject.name; - self.el.setAttribute('data-layer-name', 'Street • ' + streetmixName); - const streetWidth = streetmixSegments.reduce( - (streetWidth, segmentData) => streetWidth + segmentData.width, - 0 - ); - self.el.setAttribute('managed-street', 'width', streetWidth); + this.el.setAttribute('data-layer-name', 'Street • ' + streetmixName); + const streetWidth = streetmixSegments.reduce( + (streetWidth, segmentData) => streetWidth + segmentData.width, + 0 + ); + this.el.setAttribute('managed-street', 'width', streetWidth); - const segmentEls = parseStreetmixSegments( - streetmixSegments, - data.showStriping, - data.length, - data.showVehicles - ); - self.el.append(...segmentEls); - - self.pendingEntities = segmentEls; - // for each pending entity Listen for loaded event - for (const entity of self.pendingEntities) { - entity.addEventListener( - 'loaded', - () => { - self.onEntityLoaded(entity); - }, - { once: true } - ); - } - // Set up a promise that resolves when all entities are loaded - self.allLoadedPromise = new Promise((resolve) => { - self.resolveAllLoaded = resolve; - }); - // When all entities are loaded, do something with them - self.allLoadedPromise.then(() => { - self.applyJustification(); - self.createOrUpdateJustifiedDirtBox(); - AFRAME.INSPECTOR.selectEntity(self.el); - }); - } else { - // We reached our target server, but it returned an error - console.log( - '[streetmix-loader]', - 'Loading Error: We reached the target server, but it returned an error' + const segmentEls = parseStreetmixSegments( + streetmixSegments, + data.showStriping, + data.length, + data.showVehicles + ); + this.el.append(...segmentEls); + + this.pendingEntities = segmentEls; + // for each pending entity Listen for loaded event + for (const entity of this.pendingEntities) { + entity.addEventListener( + 'loaded', + () => { + this.onEntityLoaded(entity); + }, + { once: true } ); } - }; - request.onerror = function () { - // There was a connection error of some sort - console.log( - '[streetmix-loader]', - 'Loading Error: There was a connection error of some sort' - ); - }; - request.send(); + + // Set up a promise that resolves when all entities are loaded + this.allLoadedPromise = new Promise((resolve) => { + this.resolveAllLoaded = resolve; + }); + + // When all entities are loaded, do something with them + this.allLoadedPromise.then(() => { + this.applyJustification(); + this.createOrUpdateJustifiedDirtBox(); + AFRAME.INSPECTOR.selectEntity(this.el); + }); + } catch (error) { + console.error('[managed-street] loader', 'Loading Error:', error); + } }, onEntityLoaded: function (entity) { // Remove from pending set From 4f166e3226a84510f6e68a5a500a7ba6a7d91bbe Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:01:34 -0800 Subject: [PATCH 111/118] merge managed street sidebar changes ugly but it works --- src/editor/components/components/Sidebar.js | 46 ++++++++++++++++++--- 1 file changed, 41 insertions(+), 5 deletions(-) diff --git a/src/editor/components/components/Sidebar.js b/src/editor/components/components/Sidebar.js index 32c3bd573..4d460c6a6 100644 --- a/src/editor/components/components/Sidebar.js +++ b/src/editor/components/components/Sidebar.js @@ -11,9 +11,17 @@ import PropTypes from 'prop-types'; import React from 'react'; import capitalize from 'lodash-es/capitalize'; import classnames from 'classnames'; -import { ArrowRightIcon, Object24Icon } from '../../icons'; +import { + ArrowRightIcon, + Object24Icon, + SegmentIcon, + ManagedStreetIcon +} from '../../icons'; import GeoSidebar from './GeoSidebar'; // Make sure to create and import this new component import IntersectionSidebar from './IntersectionSidebar'; +import StreetSegmentSidebar from './StreetSegmentSidebar'; +import ManagedStreetSidebar from './ManagedStreetSidebar'; +import AdvancedComponents from './AdvancedComponents'; export default class Sidebar extends React.Component { static propTypes = { entity: PropTypes.object, @@ -90,7 +98,13 @@ export default class Sidebar extends React.Component { <>
- + {entity.getAttribute('managed-street') ? ( + + ) : entity.getAttribute('street-segment') ? ( + + ) : ( + + )} {entityName || formattedMixin}
@@ -98,7 +112,8 @@ export default class Sidebar extends React.Component {
- {entity.id !== 'reference-layers' ? ( + {entity.id !== 'reference-layers' && + !entity.getAttribute('street-segment') ? ( <> {!!entity.mixinEls.length && } {entity.hasAttribute('data-no-transform') ? ( @@ -128,10 +143,25 @@ export default class Sidebar extends React.Component { {entity.getAttribute('intersection') && ( )} + {entity.getAttribute('managed-street') && ( + + )} ) : ( - + <> + {entity.getAttribute('street-segment') && ( + <> + +
+ +
+ + )} + {entity.id === 'reference-layers' && ( + + )} + )}
@@ -146,7 +176,13 @@ export default class Sidebar extends React.Component { {entityName || formattedMixin}
- + {entity.getAttribute('managed-street') ? ( + + ) : entity.getAttribute('street-segment') ? ( + + ) : ( + + )}
From c9b4908afb68e31019b9b94a8e56faa47d0f1b79 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:04:04 -0800 Subject: [PATCH 112/118] updates to street segment logic --- src/components/street-segment.js | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 699e6c6b9..0f1212245 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -143,16 +143,7 @@ AFRAME.registerComponent('street-segment', { schema: { type: { type: 'string', // value not used by component, used in React app instead - oneOf: [ - 'drive-lane', - 'bus-lane', - 'bike-lane', - 'sidewalk', - 'parking-lane', - 'divider', - 'grass', - 'rail' - ] + oneOf: [Object.keys(TYPES)] }, width: { type: 'number' @@ -191,9 +182,9 @@ AFRAME.registerComponent('street-segment', { this.generatedComponents = []; this.types = TYPES; // default segment types }, - createGeneratedComponentsFromType: function (typeObject) { + generateComponentsFromSegmentObject: function (segmentObject) { // use global preset data to create the generated components for a given segment type - const componentsToGenerate = typeObject.generated; + const componentsToGenerate = segmentObject.generated; // for each of clones, stencils, rail, pedestrians, etc. if (componentsToGenerate?.clones?.length > 0) { @@ -263,7 +254,7 @@ AFRAME.registerComponent('street-segment', { let typeObject = this.types[this.data.type]; this.updateGeneratedComponentsList(); // if components were created through streetmix or streetplan import this.remove(); - this.createGeneratedComponentsFromType(typeObject); // add components for this type + this.generateComponentsFromSegmentObject(typeObject); // add components for this type this.updateSurfaceFromType(typeObject); // update surface color, surface, level } // propagate change of direction to generated components is solo changed From a7b34a6cbf6fd4cc20ed42fa3703b2c2b17f9871 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:06:43 -0800 Subject: [PATCH 113/118] do append child earlier --- src/components/street-generated-pedestrians.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/components/street-generated-pedestrians.js b/src/components/street-generated-pedestrians.js index 2f3e2acca..7fc75a1ef 100644 --- a/src/components/street-generated-pedestrians.js +++ b/src/components/street-generated-pedestrians.js @@ -77,6 +77,7 @@ AFRAME.registerComponent('street-generated-pedestrians', { // Create pedestrians for (let i = 0; i < totalPedestrians; i++) { const pedestrian = document.createElement('a-entity'); + this.el.appendChild(pedestrian); // Set random position within bounds const position = { @@ -106,7 +107,6 @@ AFRAME.registerComponent('street-generated-pedestrians', { pedestrian.setAttribute('data-no-transform', ''); pedestrian.setAttribute('data-layer-name', 'Generated Pedestrian'); - this.el.appendChild(pedestrian); this.createdEntities.push(pedestrian); } }, From 997ac2391d3d46970e693a1fa7842d42f86c0d7c Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:10:25 -0800 Subject: [PATCH 114/118] strings to numbers --- src/components/street-segment.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 0f1212245..e60e89a14 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -32,8 +32,8 @@ const TYPES = { mode: 'random', modelsArray: 'sedan-rig, box-truck-rig, self-driving-waymo-car, suv-rig, motorbike', - spacing: '7.3', - count: '4' + spacing: 7.3, + count: 4 } ] } @@ -48,15 +48,15 @@ const TYPES = { { mode: 'random', model: 'bus', - spacing: '15', - count: '1' + spacing: 15, + count: 1 } ], stencil: [ { stencils: 'word-only, word-taxi, word-bus', - spacing: '40', - padding: '10' + spacing: 40, + padding: 10 } ] } @@ -70,8 +70,8 @@ const TYPES = { stencil: [ { model: 'bike-arrow', - cycleOffset: '0.3', - spacing: '20' + cycleOffset: 0.3, + spacing: 20 } ], clones: [ @@ -79,8 +79,8 @@ const TYPES = { mode: 'random', modelsArray: 'cyclist-cargo, cyclist1, cyclist2, cyclist3, cyclist-dutch, cyclist-kid, ElectricScooter_1', - spacing: '2.03', - count: '4' + spacing: 2.03, + count: 4 } ] } From 6ab92861bdcd2b4529a2b4d53a4c0e98aa856d12 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:13:06 -0800 Subject: [PATCH 115/118] use dot syntax --- src/editor/components/components/ManagedStreetSidebar.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/editor/components/components/ManagedStreetSidebar.js b/src/editor/components/components/ManagedStreetSidebar.js index 199f60bcc..7f2b44ad1 100644 --- a/src/editor/components/components/ManagedStreetSidebar.js +++ b/src/editor/components/components/ManagedStreetSidebar.js @@ -16,8 +16,8 @@ const ManagedStreetSidebar = ({ entity }) => { key="length" name="length" label="Street Length" - schema={component.schema['length']} - data={component.data['length']} + schema={component.schema.length} + data={component.data.length} componentname={componentName} isSingle={false} entity={entity} @@ -26,8 +26,8 @@ const ManagedStreetSidebar = ({ entity }) => { key="width" name="width" label="Street Width" - schema={component.schema['width']} - data={component.data['width']} + schema={component.schema.width} + data={component.data.width} componentname={componentName} isSingle={false} entity={entity} From ec06a424fe480d69a00d50ff1833899ffe888662 Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:28:30 -0800 Subject: [PATCH 116/118] revert using keys(TYPES) for oneOf type list --- src/components/street-segment.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index e60e89a14..e4799fa07 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -143,7 +143,16 @@ AFRAME.registerComponent('street-segment', { schema: { type: { type: 'string', // value not used by component, used in React app instead - oneOf: [Object.keys(TYPES)] + oneOf: [ + 'drive-lane', + 'bus-lane', + 'bike-lane', + 'sidewalk', + 'parking-lane', + 'divider', + 'grass', + 'rail' + ] }, width: { type: 'number' From e0763fcb03d468185e296f7566e801e5c3c223aa Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:31:31 -0800 Subject: [PATCH 117/118] use object instead of template literal for components --- src/components/street-segment.js | 58 +++++++++++++++++++++----------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index e4799fa07..937444254 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -199,39 +199,57 @@ AFRAME.registerComponent('street-segment', { if (componentsToGenerate?.clones?.length > 0) { componentsToGenerate.clones.forEach((clone, index) => { if (clone?.modelsArray?.length > 0) { - this.el.setAttribute( - `street-generated-clones__${index}`, - `mode: ${clone.mode}; modelsArray: ${clone.modelsArray}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; count: ${clone.count};` - ); + this.el.setAttribute(`street-generated-clones__${index}`, { + mode: clone.mode, + modelsArray: clone.modelsArray, + length: this.data.length, + spacing: clone.spacing, + direction: this.data.direction, + count: clone.count + }); } else { - this.el.setAttribute( - `street-generated-clones__${index}`, - `mode: ${clone.mode}; model: ${clone.model}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; count: ${clone.count};` - ); + this.el.setAttribute(`street-generated-clones__${index}`, { + mode: clone.mode, + model: clone.model, + length: this.data.length, + spacing: clone.spacing, + direction: this.data.direction, + count: clone.count + }); } }); } + if (componentsToGenerate?.stencil?.length > 0) { componentsToGenerate.stencil.forEach((clone, index) => { if (clone?.stencils?.length > 0) { - this.el.setAttribute( - `street-generated-stencil__${index}`, - `stencils: ${clone.stencils}; length: ${this.data.length}; spacing: ${clone.spacing}; direction: ${this.data.direction}; padding: ${clone.padding};` - ); + this.el.setAttribute(`street-generated-stencil__${index}`, { + stencils: clone.stencils, + length: this.data.length, + spacing: clone.spacing, + direction: this.data.direction, + padding: clone.padding + }); } 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};` - ); + this.el.setAttribute(`street-generated-stencil__${index}`, { + model: clone.model, + length: this.data.length, + spacing: clone.spacing, + direction: this.data.direction, + count: clone.count + }); } }); } + if (componentsToGenerate?.pedestrians?.length > 0) { componentsToGenerate.pedestrians.forEach((pedestrian, index) => { - this.el.setAttribute( - `street-generated-pedestrians__${index}`, - `segmentWidth: ${this.data.width}; density: ${pedestrian.density}; length: ${this.data.length}; direction: ${this.data.direction};` - ); + this.el.setAttribute(`street-generated-pedestrians__${index}`, { + segmentWidth: this.data.width, + density: pedestrian.density, + length: this.data.length, + direction: this.data.direction + }); }); } }, From 4d640324d8bf9d693bac56d8cdf2bd99d7e6ef6c Mon Sep 17 00:00:00 2001 From: Kieran Farr Date: Sun, 15 Dec 2024 11:37:59 -0800 Subject: [PATCH 118/118] fix segment resize logic --- src/components/street-segment.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/street-segment.js b/src/components/street-segment.js index 937444254..ecac7345c 100644 --- a/src/components/street-segment.js +++ b/src/components/street-segment.js @@ -310,6 +310,7 @@ 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(); } },