Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use random seed #971

Merged
merged 6 commits into from
Dec 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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)
}
Loading