diff --git a/src/aframe-streetmix-parsers.js b/src/aframe-streetmix-parsers.js index 2ac9f62a..56d84f17 100644 --- a/src/aframe-streetmix-parsers.js +++ b/src/aframe-streetmix-parsers.js @@ -4,6 +4,51 @@ 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': { + 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 + }, + rail: { + surface: 'asphalt', + color: COLORS.white + } +}; + function cloneMixinAsChildren({ objectMixinId = '', parentEl = null, @@ -190,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" @@ -199,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 = { @@ -214,46 +259,6 @@ 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 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); @@ -275,7 +280,6 @@ function getZPositions(start, end, step) { function createSidewalkClonedVariants( segmentWidthInMeters, density, - elevationPosY = 0, streetLength, direction = 'random', animated = false @@ -299,8 +303,8 @@ function createSidewalkClonedVariants( densityFactors[density] * streetLength, 10 ); - const dividerParentEl = createParentElement('pedestrians-parent'); - dividerParentEl.setAttribute('position', { y: elevationPosY }); + 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 = @@ -327,7 +331,6 @@ function createSidewalkClonedVariants( 1.4, streetLength, xVal, - yVal, zVal, animationDirection ); @@ -338,30 +341,17 @@ function createSidewalkClonedVariants( return dividerParentEl; } -function getBikeLaneMixin(variant) { - if (variant === 'red') { - return 'surface-red bike-lane'; +function getSegmentColor(variant) { + if ((variant === 'red') | (variant === 'colored')) { + return COLORS.red; } if (variant === 'blue') { - return 'surface-blue bike-lane'; + return COLORS.blue; } - if (variant === 'green') { - return 'surface-green bike-lane'; + if ((variant === 'green') | (variant === 'grass')) { + return COLORS.green; } - 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'; + return COLORS.white; } function getDimensions(object3d) { @@ -394,49 +384,11 @@ 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 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 ) { @@ -450,7 +402,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 }; @@ -458,8 +410,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 }; @@ -511,7 +463,6 @@ function createDriveLaneElement( return createSidewalkClonedVariants( segmentWidthInMeters, 'normal', - 0, streetLength, direction, animated @@ -585,7 +536,6 @@ function createDriveLaneElement( speed, streetLength, 0, - 0, positionZ, direction ); @@ -629,158 +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; - } - 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; - } - 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; - } - 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; @@ -812,80 +610,6 @@ function createWayfindingElements() { 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 function createCenteredStreetElement(segments) { const streetEl = document.createElement('a-entity'); @@ -898,42 +622,12 @@ 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]}` - ); +function calculateHeight(elevation) { + const stepLevel = 0.15; + if (elevation <= 0) { + return stepLevel; } - - return segmentEl; + return stepLevel * (elevation + 1); } function createSeparatorElement( @@ -951,7 +645,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) { @@ -1013,6 +709,7 @@ 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); @@ -1034,12 +731,6 @@ function processSegments( // elevation property from streetmix segment const elevation = segments[i].elevation; - 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 = @@ -1048,37 +739,28 @@ 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 = []; // 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: 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); + 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' ) { + segmentPreset = 'bike-lane'; // use bike lane road material // 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 - 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', @@ -1089,36 +771,49 @@ function processSegments( }); // add this stencil stuff to the segment parent segmentParentEl.append(stencilsParentEl); - segmentParentEl.append( - createMicroMobilityElement( - variantList, - segments[i].type, - elevationPosY, - 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' || segments[i].type === 'streetcar' ) { - // get the mixin id for a bus lane - groundMixinId = getBusLaneMixin(variantList[1]); + 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?) - 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) { + 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 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 + 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') { markerMixinId = markerMixinId.replace(/left|right/g, function (m) { @@ -1135,7 +830,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, @@ -1149,7 +844,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({ @@ -1163,137 +858,123 @@ 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({ - 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; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: bollard; spacing: 4; length: ${length}` + ); } else if (segments[i].type === 'divider' && variantList[0] === 'flowers') { - groundMixinId = 'grass'; - segmentParentEl.append( - createDividerVariant('flowers', clonedObjectRadius, 2.25) + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-flowers; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'divider' && variantList[0] === 'planting-strip' ) { - groundMixinId = 'grass'; - segmentParentEl.append( - createDividerVariant('planting-strip', clonedObjectRadius, 2.25) + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-planting-strip; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'divider' && variantList[0] === 'planter-box' ) { - groundMixinId = 'grass'; - segmentParentEl.append( - createDividerVariant('planter-box', clonedObjectRadius, 2.45) + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-planter-box; spacing: 2.45; length: ${length}` ); } else if ( segments[i].type === 'divider' && variantList[0] === 'palm-tree' ) { - groundMixinId = 'grass'; - const treesParentEl = createTreesParentElement(); - cloneMixinAsChildren({ - objectMixinId: 'palm-tree', - parentEl: treesParentEl, - randomY: true, - radius: clonedObjectRadius - }); - segmentParentEl.append(treesParentEl); + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: palm-tree; length: ${length}` + ); } else if ( segments[i].type === 'divider' && variantList[0] === 'big-tree' ) { - groundMixinId = 'grass'; - const treesParentEl = createTreesParentElement(); - cloneMixinAsChildren({ - objectMixinId: 'tree3', - parentEl: treesParentEl, - randomY: true, - radius: clonedObjectRadius - }); - segmentParentEl.append(treesParentEl); + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: tree3; length: ${length}` + ); } else if (segments[i].type === 'divider' && variantList[0] === 'bush') { - groundMixinId = 'grass'; - segmentParentEl.append( - createDividerVariant('bush', clonedObjectRadius, 2.25) + segmentPreset = 'grass'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-bush; spacing: 2.25; length: ${length}` ); } else if (segments[i].type === 'divider' && variantList[0] === 'dome') { - groundMixinId = 'divider'; - segmentParentEl.append( - createDividerVariant('dome', clonedObjectRadius, 2.25) + segmentPreset = 'divider'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: dividers-dome; spacing: 2.25; length: ${length}` ); - repeatCount[0] = 1; - repeatCount[1] = parseInt(length) / 4; } else if (segments[i].type === 'divider') { - groundMixinId = 'divider'; - repeatCount[0] = 1; - repeatCount[1] = parseInt(length) / 4; + segmentPreset = 'divider'; } else if ( segments[i].type === 'temporary' && variantList[0] === 'barricade' ) { - groundMixinId = 'drive-lane'; - segmentParentEl.append( - createClonedVariants('temporary-barricade', clonedObjectRadius, 2.25) + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: temporary-barricade; spacing: 2.25; length: ${length}` ); } else if ( segments[i].type === 'temporary' && variantList[0] === 'traffic-cone' ) { - groundMixinId = 'drive-lane'; - segmentParentEl.append( - createClonedVariants('temporary-traffic-cone', clonedObjectRadius, 2.25) + segmentPreset = 'drive-lane'; + 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' ) { - groundMixinId = 'drive-lane'; - segmentParentEl.append( - createClonedVariants( - 'temporary-jersey-barrier-plastic', - clonedObjectRadius, - 2.25 - ) + segmentPreset = 'drive-lane'; + 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' ) { - groundMixinId = 'drive-lane'; - segmentParentEl.append( - createClonedVariants( - 'temporary-jersey-barrier-concrete', - clonedObjectRadius, - 2.93 - ) + segmentPreset = 'drive-lane'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: temporary-jersey-barrier-concrete; spacing: 2.93; length: ${length}` ); } else if ( segments[i].type === 'bus-lane' || segments[i].type === 'brt-lane' ) { - groundMixinId = getBusLaneMixin(variantList[1]); - - segmentParentEl.append( - createBusElement(variantList, length, showVehicles) - ); - + // get the color for a bus lane + segmentColor = getSegmentColor(variantList[1]); + + if (showVehicles) { + const rotationY = variantList[0] === 'inbound' ? 0 : 180; + segmentParentEl.setAttribute( + 'street-generated-random', + `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: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: 'stencils word-bus', @@ -1306,7 +987,7 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: 10 }); cloneMixinAsChildren({ @@ -1320,7 +1001,7 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: 20 }); cloneMixinAsChildren({ @@ -1333,33 +1014,39 @@ 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 - ) - ); + 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') { - groundMixinId = 'drive-lane'; - segmentParentEl.append(createFoodTruckElement(variantList, length)); - } else if (segments[i].type === 'flex-zone') { - groundMixinId = 'bright-lane'; - segmentParentEl.append( - createFlexZoneElement(variantList, length, showVehicles) + 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;` ); - + } 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-random', + `model: ${objectMixinId}; length: ${length}; placeLength: 5; facing: ${rotationCloneY}; count: 4;` + ); + } let reusableObjectStencilsParentEl; - reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: 5 }); cloneMixinAsChildren({ @@ -1373,7 +1060,7 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015, + y: 0.015, z: -5 }); cloneMixinAsChildren({ @@ -1392,7 +1079,6 @@ function processSegments( createSidewalkClonedVariants( segmentWidthInMeters, variantList[0], - elevationPosY, length, 'random', isAnimated @@ -1401,174 +1087,150 @@ 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}; cycleOffset: 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}; cycleOffset: 0.1` + ); } } 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; - cloneMixinAsChildren({ - objectMixinId: 'bikerack', - parentEl: bikeRacksParentEl, - rotation: '0 ' + rotationCloneY + ' 0', - radius: clonedObjectRadius - }); + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: bikerack; length: ${length}; facing: ${rotationCloneY}; cycleOffset: 0.2` + ); // add bike racks to the segment parent - segmentParentEl.append(bikeRacksParentEl); } else if (segments[i].type === 'magic-carpet') { - groundMixinId = 'drive-lane'; - segmentParentEl.append(createMagicCarpetElement(showVehicles)); + segmentPreset = 'drive-lane'; + 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') { - groundMixinId = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; - segmentParentEl.append(createOutdoorDining(length, elevationPosY)); + segmentPreset = variantList[1] === 'road' ? 'drive-lane' : 'sidewalk'; + segmentParentEl.setAttribute( + 'street-generated-random', + `model: outdoor_dining; length: ${length}; placeLength: 2.27; count: 5;` + ); } else if (segments[i].type === 'parklet') { - groundMixinId = 'drive-lane'; - segmentParentEl.append(createParkletElement(length, variantList)); + 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};` + ); } else if (segments[i].type === 'bikeshare') { - // make the parent for all the stations - segmentParentEl.append( - createBikeShareStationElement(variantList, elevationPosY) + 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' ? '0 180 0' : '0 0 0'; - const utilityPoleElems = createClonedVariants( - 'utility_pole', - clonedObjectRadius, - 15, - rotation + const rotationCloneY = variantList[0] === 'right' ? 180 : 0; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: utility_pole; length: ${length}; cycleOffset: 0.25; facing: ${rotationCloneY}` ); - 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); + const objectMixinId = + variantList[0] === 'palm-tree' ? 'palm-tree' : 'tree3'; + segmentParentEl.setAttribute( + 'street-generated-fixed', + `model: ${objectMixinId}; length: ${length}; randomFacing: true;` + ); } 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') { - 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__2', + `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__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' ) { - // 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, elevationPosY) + segmentParentEl.setAttribute( + 'street-generated-single', + `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' ) { - groundMixinId = 'markings dashed-stripe'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + 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') { - groundMixinId = 'markings solid-stripe'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + segmentPreset = 'solid-stripe'; + positionY = 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 + segmentPreset = 'solid-doubleyellow'; + positionY = 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 + 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); @@ -1576,21 +1238,21 @@ function processSegments( 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 + 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' ) { - groundMixinId = 'markings yellow solid-dashed'; - positionY = elevationPosY + 0.01; // make sure the lane marker is above the asphalt + 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') { let reusableObjectStencilsParentEl; - groundMixinId = 'bright-lane'; + segmentPreset = 'parking-lane'; let parkingMixin = 'stencils parking-t'; const carCount = 5; @@ -1621,7 +1283,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; @@ -1639,7 +1301,7 @@ function processSegments( ); if (variantList[1] === 'left') { reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: parkingMixin, @@ -1652,7 +1314,7 @@ function processSegments( }); } else { reusableObjectStencilsParentEl = createStencilsParentElement({ - y: elevationPosY + 0.015 + y: 0.015 }); cloneMixinAsChildren({ objectMixinId: parkingMixin, @@ -1668,31 +1330,36 @@ function processSegments( segmentParentEl.append(reusableObjectStencilsParentEl); } + // if this thing is a sidewalk, make segmentPreset sidewalk if (streetmixParsersTested.isSidewalk(segments[i].type)) { - groundMixinId = 'sidewalk'; - repeatCount[0] = segmentWidthInMeters / 1.5; - // every 2 meters repeat sidewalk texture - repeatCount[1] = parseInt(length / 2); + segmentPreset = 'sidewalk'; } - // add new object if (segments[i].type !== 'separator') { - segmentParentEl.append( - createSegmentElement( - segmentWidthInMeters, - positionY, - groundMixinId, - length, - repeatCount, - elevation - ) + 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 ); } else { segmentParentEl.append( createSeparatorElement( positionY, rotationY, - groundMixinId, + segmentPreset, length, repeatCount, elevation @@ -1711,11 +1378,11 @@ 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 - dirtBox.setAttribute('material', 'color: #664B00;'); + dirtBox.setAttribute('material', `color: ${COLORS.brown};`); dirtBox.setAttribute('data-layer-name', 'Underground'); streetParentEl.append(dirtBox); return streetParentEl; @@ -1725,7 +1392,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', @@ -1856,7 +1522,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') { @@ -1868,34 +1533,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); } }); diff --git a/src/assets.js b/src/assets.js index e7f666f2..69f48013 100644 --- a/src/assets.js +++ b/src/assets.js @@ -128,63 +128,64 @@ function buildAssetHTML(assetUrl, categories) { - + `, 'segment-colors': ` - `, 'lane-separator': ` - - - - - - - - + + + + + + + + + + `, stencils: ` - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + `, 'vehicles-transit': ` - - + + `, @@ -229,7 +230,7 @@ function buildAssetHTML(assetUrl, categories) { - + @@ -383,7 +384,7 @@ Unused assets kept commented here for future reference - + diff --git a/src/components/street-generated-fixed.js b/src/components/street-generated-fixed.js new file mode 100644 index 00000000..6c040141 --- /dev/null +++ b/src/components/street-generated-fixed.js @@ -0,0 +1,107 @@ +/* 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' + }, + rotationX: { + default: 0, + type: 'number' + } + // 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', + `${data.rotationX} ${Math.random() * 360} 0` + ); + } else { + 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 + 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-random.js b/src/components/street-generated-random.js new file mode 100644 index 00000000..8792ec77 --- /dev/null +++ b/src/components/street-generated-random.js @@ -0,0 +1,127 @@ +/* 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/components/street-generated-single.js b/src/components/street-generated-single.js new file mode 100644 index 00000000..71cc186c --- /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/components/street-segment.js b/src/components/street-segment.js new file mode 100644 index 00000000..288d12ce --- /dev/null +++ b/src/components/street-segment.js @@ -0,0 +1,177 @@ +/* global AFRAME */ + +/* + + + + + + +*/ + +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); + } +}); + +AFRAME.registerComponent('street-segment', { + schema: { + width: { + type: 'number' + }, + length: { + type: 'number' + }, + elevation: { + type: 'int', + default: 0 + }, + direction: { + type: 'string', + oneOf: ['inbound', 'outbound'] + }, + surface: { + type: 'string', + default: 'asphalt', + oneOf: [ + 'asphalt', + 'concrete', + 'grass', + 'sidewalk', + 'gravel', + 'sand', + 'none', + 'solid' + ] + }, + color: { + type: 'color' + } + }, + init: function () { + this.height = 0.2; // default height of segment surface box + }, + 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; + } + this.clearMesh(); + 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 stepLevel = 0.15; + if (elevation <= 0) { + return stepLevel; + } + return stepLevel * (elevation + 1); + }, + clearMesh: function () { + // remove the geometry from the entity + this.el.removeAttribute('geometry'); + this.el.removeAttribute('material'); + }, + remove: function () { + this.clearMesh(); + }, + generateMesh: function (data) { + // create geometry + this.el.setAttribute( + 'geometry', + `primitive: below-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', + grass: 'grass-texture', + sidewalk: 'seamless-sidewalk', + gravel: 'compacted-gravel-texture', + sand: 'sandy-asphalt-texture', + hatched: 'hatched-base', + none: 'none', + solid: '' + }; + let textureSourceId = textureMaps[data.surface]; + + // calculate the repeatCount for the material + let [repeatX, repeatY, offsetX] = this.calculateTextureRepeat( + data.length, + data.width, + textureSourceId + ); + + this.el.setAttribute( + 'material', + `src: #${textureMaps[data.surface]}; + roughness: 0.8; + repeat: ${repeatX} ${repeatY}; + offset: ${offsetX} 0; + color: ${data.color}` + ); + + this.el.setAttribute('shadow', ''); + + this.el.setAttribute( + 'material', + 'visible', + textureMaps[data.surface] !== 'none' + ); + + 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; // we could get rid of this using cropped texture for asphalt + if (textureSourceId === 'seamless-bright-road') { + repeatX = 0.6; + repeatY = 15; + } else if (textureSourceId === 'seamless-sandy-road') { + 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; + } else if (textureSourceId === 'hatched-base') { + repeatX = 1; + repeatY = length / 4; + offsetX = 0; + } + return [repeatX, repeatY, offsetX]; + } +}); diff --git a/src/index.js b/src/index.js index b8817c5e..311191f2 100644 --- a/src/index.js +++ b/src/index.js @@ -21,6 +21,10 @@ require('./components/street-geo.js'); 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('./editor/index.js'); const state = useStore.getState();