Skip to content

Commit

Permalink
Merge pull request #971 from 3DStreet/use-random-seed
Browse files Browse the repository at this point in the history
Use random seed
  • Loading branch information
kfarr authored Dec 24, 2024
2 parents ed0d5db + f62f599 commit 4cde5ab
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 34 deletions.
28 changes: 22 additions & 6 deletions src/components/street-generated-clones.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
/* global AFRAME */
import { createRNG } from '../lib/rng';

AFRAME.registerComponent('street-generated-clones', {
multiple: true,
Expand All @@ -10,6 +11,7 @@ AFRAME.registerComponent('street-generated-clones', {
positionX: { default: 0, type: 'number' },
positionY: { default: 0, type: 'number' },
facing: { default: 0, type: 'number' }, // Y Rotation in degrees
seed: { default: 0, type: 'int' }, // random seed for random and randomFacing mode
randomFacing: { default: false, type: 'boolean' },
direction: { type: 'string', oneOf: ['none', 'inbound', 'outbound'] }, // not used if facing defined?

Expand Down Expand Up @@ -40,6 +42,18 @@ AFRAME.registerComponent('street-generated-clones', {
},

update: function (oldData) {
// If mode is random or randomFacing and seed is 0, generate a random seed and return,
// the update will be called again because of the setAttribute.
if (this.data.mode === 'random' || this.data.randomFacing) {
if (this.data.seed === 0) {
const newSeed = Math.floor(Math.random() * 1000000) + 1; // Add 1 to avoid seed 0
this.el.setAttribute(this.attrName, 'seed', newSeed);
return;
}
// Always recreate RNG when update is called to be sure we end of with the same clones positions for a given seed
this.rng = createRNG(this.data.seed);
}

// Clear existing entities
this.remove();

Expand Down Expand Up @@ -115,7 +129,7 @@ AFRAME.registerComponent('street-generated-clones', {
rotationY = 180 - data.facing;
}
if (data.randomFacing) {
rotationY = Math.random() * 360;
rotationY = this.rng() * 360;
}
clone.setAttribute('rotation', `0 ${rotationY} 0`);

Expand All @@ -131,9 +145,7 @@ AFRAME.registerComponent('street-generated-clones', {
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.modelsArray[Math.floor(this.rng() * data.modelsArray.length)];
}
return data.model;
},
Expand All @@ -151,8 +163,12 @@ AFRAME.registerComponent('street-generated-clones', {
// Apply the offset similar to fixed mode
return start + idx * correctedSpacing;
});
// Use seeded random for shuffling
for (let i = positions.length - 1; i > 0; i--) {
const j = Math.floor(this.rng() * (i + 1));
[positions[i], positions[j]] = [positions[j], positions[i]];
}

// Randomly select positions
return positions.sort(() => 0.5 - Math.random()).slice(0, count);
return positions.slice(0, count);
}
});
67 changes: 39 additions & 28 deletions src/components/street-generated-pedestrians.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* global AFRAME */
import { createRNG } from '../lib/rng';

// 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
},
Expand All @@ -15,23 +14,20 @@ AFRAME.registerComponent('street-generated-pedestrians', {
oneOf: ['empty', 'sparse', 'normal', 'dense']
},
length: {
// length in meters of linear path to fill with clones
type: 'number'
},
direction: {
type: 'string',
default: 'none',
oneOf: ['none', '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
},
seed: {
type: 'int',
default: 0
}
},

Expand All @@ -47,12 +43,22 @@ AFRAME.registerComponent('street-generated-pedestrians', {

remove: function () {
this.createdEntities.forEach((entity) => entity.remove());
this.createdEntities.length = 0; // Clear the array
this.createdEntities.length = 0;
},

update: function (oldData) {
const data = this.data;

// Handle seed initialization
if (this.data.seed === 0) {
const newSeed = Math.floor(Math.random() * 1000000) + 1;
this.el.setAttribute(this.attrName, 'seed', newSeed);
return;
}

// Create seeded RNG
this.rng = createRNG(this.data.seed);

// Clean up old entities
this.remove();

Expand All @@ -67,7 +73,7 @@ AFRAME.registerComponent('street-generated-pedestrians', {
this.densityFactors[data.density] * data.length
);

// Get available z positions
// Get Z positions using seeded randomization
const zPositions = this.getZPositions(
-data.length / 2,
data.length / 2,
Expand All @@ -79,25 +85,23 @@ AFRAME.registerComponent('street-generated-pedestrians', {
const pedestrian = document.createElement('a-entity');
this.el.appendChild(pedestrian);

// Set random position within bounds
// Set seeded random position within bounds
const position = {
x: this.getRandomArbitrary(xRange.min, xRange.max),
y: data.positionY,
z: zPositions.pop()
z: zPositions[i]
};
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 model variant using seeded random
const variantNumber = this.getRandomIntInclusive(1, 16);
pedestrian.setAttribute('mixin', `char${variantNumber}`);

// Set rotation based on direction
if (data.direction === 'none' && Math.random() < 0.5) {
pedestrian.setAttribute('rotation', '0 180 0');
// Set rotation based on direction and seeded random
if (data.direction === 'none') {
if (this.rng() < 0.5) {
pedestrian.setAttribute('rotation', '0 180 0');
}
} else if (data.direction === 'outbound') {
pedestrian.setAttribute('rotation', '0 180 0');
}
Expand All @@ -111,22 +115,29 @@ AFRAME.registerComponent('street-generated-pedestrians', {
}
},

// Helper methods from legacy function
// Helper methods now using seeded RNG
getRandomIntInclusive: function (min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min + 1) + min);
return Math.floor(this.rng() * (max - min + 1) + min);
},

getRandomArbitrary: function (min, max) {
return Math.random() * (max - min) + min;
return this.rng() * (max - min) + min;
},

getZPositions: function (start, end, step) {
const len = Math.floor((end - start) / step) + 1;
const arr = Array(len)
const positions = Array(len)
.fill()
.map((_, idx) => start + idx * step);
return arr.sort(() => 0.5 - Math.random());

// Use seeded shuffle (Fisher-Yates algorithm with seeded RNG)
for (let i = positions.length - 1; i > 0; i--) {
const j = Math.floor(this.rng() * (i + 1));
[positions[i], positions[j]] = [positions[j], positions[i]];
}

return positions;
}
});
12 changes: 12 additions & 0 deletions src/lib/rng.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export function createRNG(seed) {
// Create a RNG with the specified seed
// Mulberry32 PRNG implementation
return (function (a) {
return function () {
var t = (a += 0x6d2b79f5);
t = Math.imul(t ^ (t >>> 15), t | 1);
t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
};
})(seed)
}

0 comments on commit 4cde5ab

Please sign in to comment.