fix: harden array builder editing workflows
This commit is contained in:
+71
-29
@@ -63,7 +63,10 @@ function microphoneFromPosition(position, index) {
|
||||
}
|
||||
|
||||
function pointKey(position) {
|
||||
return position.map((value) => rounded(value).toFixed(COORDINATE_DIGITS)).join(",");
|
||||
return position.map((value) => {
|
||||
const number = finiteNumber(value, "좌표");
|
||||
return Object.is(number, -0) ? "0" : String(number);
|
||||
}).join(",");
|
||||
}
|
||||
|
||||
function linearValue(index, count, minimum, maximum) {
|
||||
@@ -218,16 +221,43 @@ function randomPositions(options, bounds, shape) {
|
||||
const random = createSeededRandom(options.seed ?? 1);
|
||||
const positions = [];
|
||||
const keys = new Set();
|
||||
const maximumAttempts = Math.max(2000, count * 500);
|
||||
const cellSize = Math.max(minimumSpacing, 10 ** -COORDINATE_DIGITS);
|
||||
const spatialCells = minimumSpacingSquared > 0 ? new Map() : null;
|
||||
const cellCoordinates = (position) => [
|
||||
Math.floor(position[0] / cellSize),
|
||||
Math.floor(position[1] / cellSize),
|
||||
];
|
||||
const cellKey = (x, y) => String(x) + "," + String(y);
|
||||
const violatesMinimumSpacing = (candidate) => {
|
||||
if (!spatialCells) return false;
|
||||
const [cellX, cellY] = cellCoordinates(candidate);
|
||||
for (let offsetY = -1; offsetY <= 1; offsetY += 1) {
|
||||
for (let offsetX = -1; offsetX <= 1; offsetX += 1) {
|
||||
const neighbors = spatialCells.get(cellKey(cellX + offsetX, cellY + offsetY));
|
||||
if (neighbors?.some(
|
||||
(position) => squaredDistance(position, candidate) < minimumSpacingSquared,
|
||||
)) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const indexPosition = (position) => {
|
||||
if (!spatialCells) return;
|
||||
const [cellX, cellY] = cellCoordinates(position);
|
||||
const key = cellKey(cellX, cellY);
|
||||
const cell = spatialCells.get(key);
|
||||
if (cell) cell.push(position);
|
||||
else spatialCells.set(key, [position]);
|
||||
};
|
||||
const maximumAttempts = Math.max(2000, count * 150);
|
||||
for (let attempt = 0; attempt < maximumAttempts && positions.length < count; attempt += 1) {
|
||||
const candidate = normalizedPosition(randomCandidate(random, bounds, shape));
|
||||
const key = pointKey(candidate);
|
||||
if (keys.has(key)) continue;
|
||||
if (positions.some((position) => squaredDistance(position, candidate) < minimumSpacingSquared)) {
|
||||
continue;
|
||||
}
|
||||
if (violatesMinimumSpacing(candidate)) continue;
|
||||
keys.add(key);
|
||||
positions.push(candidate);
|
||||
indexPosition(candidate);
|
||||
}
|
||||
if (positions.length !== count) {
|
||||
throw new RangeError(
|
||||
@@ -302,35 +332,36 @@ export function prepareArrayForEditing(value) {
|
||||
}
|
||||
|
||||
export function inferApertureShape(arrayDefinition) {
|
||||
const description = String(arrayDefinition?.description ?? "");
|
||||
if (/circle|circular|ellipse|elliptic|ring|spiral/i.test(description)) return "ellipse";
|
||||
const aperture = arrayDefinition?.aperture;
|
||||
const microphones = arrayDefinition?.microphones ?? [];
|
||||
if (!aperture || microphones.length < 3) return "rectangle";
|
||||
let maximumNormalizedRadius = 0;
|
||||
for (const microphone of microphones) {
|
||||
const position = microphone?.position;
|
||||
if (!Array.isArray(position)) return "rectangle";
|
||||
const radius = Math.hypot(
|
||||
position[0] / (aperture.width / 2),
|
||||
position[1] / (aperture.height / 2),
|
||||
);
|
||||
if (radius > 1.000000001) return "rectangle";
|
||||
maximumNormalizedRadius = Math.max(maximumNormalizedRadius, radius);
|
||||
}
|
||||
return maximumNormalizedRadius >= 0.72 ? "ellipse" : "rectangle";
|
||||
const shapeWords = [
|
||||
arrayDefinition?.id,
|
||||
arrayDefinition?.name,
|
||||
arrayDefinition?.description,
|
||||
].filter(Boolean).join(" ");
|
||||
return /(^|[^a-z])(circle|circular|ellipse|elliptic|ring|spiral)([^a-z]|$)/i.test(shapeWords)
|
||||
? "ellipse"
|
||||
: "rectangle";
|
||||
}
|
||||
|
||||
export function nextMicrophoneId(arrayDefinition) {
|
||||
const used = new Set((arrayDefinition?.microphones ?? []).map((microphone) => microphone.id));
|
||||
let maximumSuffix = 0;
|
||||
let maximumSuffix = 0n;
|
||||
for (const id of used) {
|
||||
const match = /^mic-(\d+)$/.exec(id);
|
||||
if (match) maximumSuffix = Math.max(maximumSuffix, Number(match[1]));
|
||||
if (!match) continue;
|
||||
const suffix = BigInt(match[1]);
|
||||
if (suffix > maximumSuffix) maximumSuffix = suffix;
|
||||
}
|
||||
let candidate = maximumSuffix + 1;
|
||||
while (used.has(microphoneId(candidate))) candidate += 1;
|
||||
return microphoneId(candidate);
|
||||
let candidate = maximumSuffix + 1n;
|
||||
for (let attempt = 0; attempt <= used.size; attempt += 1) {
|
||||
const id = "mic-" + candidate.toString().padStart(3, "0");
|
||||
if (id.length <= 64 && !used.has(id)) return id;
|
||||
candidate += 1n;
|
||||
}
|
||||
for (let suffix = 1; suffix <= MAX_MICROPHONES + 1; suffix += 1) {
|
||||
const id = microphoneId(suffix);
|
||||
if (!used.has(id)) return id;
|
||||
}
|
||||
throw new Error("사용 가능한 마이크 ID를 찾을 수 없습니다.");
|
||||
}
|
||||
|
||||
export function addMicrophone(arrayDefinition, position, properties = {}) {
|
||||
@@ -514,8 +545,19 @@ export function arrayStatistics(arrayDefinition) {
|
||||
if (microphones.length === 0) {
|
||||
return { count: 0, centroid: [0, 0, 0], minimumSpacing: null, calibrationCount: 0 };
|
||||
}
|
||||
const centroid = [0, 1, 2].map((axis) =>
|
||||
microphones.reduce((sum, microphone) => sum + microphone.position[axis], 0) / microphones.length);
|
||||
const centroid = [0, 1, 2].map((axis) => {
|
||||
const scale = microphones.reduce(
|
||||
(maximum, microphone) => Math.max(maximum, Math.abs(microphone.position[axis])),
|
||||
0,
|
||||
);
|
||||
if (scale === 0) return 0;
|
||||
const normalizedSum = microphones.reduce(
|
||||
(sum, microphone) => sum + microphone.position[axis] / scale,
|
||||
0,
|
||||
);
|
||||
const normalizedMean = Math.max(-1, Math.min(1, normalizedSum / microphones.length));
|
||||
return normalizedMean * scale;
|
||||
});
|
||||
let minimumSpacingSquared = Infinity;
|
||||
for (let left = 0; left < microphones.length; left += 1) {
|
||||
for (let right = left + 1; right < microphones.length; right += 1) {
|
||||
|
||||
Reference in New Issue
Block a user