@chauffleet/expo-custom-map
Version:
Open source custom map library for Expo/React Native. Use your own tiles without Google Maps, Mapbox, or API keys. Created by ChaufFleet.
268 lines (237 loc) • 7.16 kB
text/typescript
// src/utils/mathUtils.ts
/**
* Clamp une valeur entre un minimum et un maximum
*/
export const clamp = (value: number, min: number, max: number): number => {
return Math.min(Math.max(value, min), max);
};
/**
* Interpolation linéaire entre deux valeurs
*/
export const lerp = (start: number, end: number, progress: number): number => {
return start + (end - start) * progress;
};
/**
* Interpolation inverse - trouve le facteur de progression entre deux valeurs
*/
export const inverseLerp = (start: number, end: number, value: number): number => {
if (start === end) return 0;
return (value - start) / (end - start);
};
/**
* Remap une valeur d'une plage à une autre
*/
export const remap = (
value: number,
fromMin: number,
fromMax: number,
toMin: number,
toMax: number
): number => {
const progress = inverseLerp(fromMin, fromMax, value);
return lerp(toMin, toMax, progress);
};
/**
* Arrondir à un nombre spécifique de décimales
*/
export const roundToDecimals = (value: number, decimals: number): number => {
const factor = Math.pow(10, decimals);
return Math.round(value * factor) / factor;
};
/**
* Vérifier si un nombre est dans une plage (inclusif)
*/
export const isInRange = (value: number, min: number, max: number): boolean => {
return value >= min && value <= max;
};
/**
* Calculer la distance euclidienne entre deux points 2D
*/
export const distance2D = (x1: number, y1: number, x2: number, y2: number): number => {
const dx = x2 - x1;
const dy = y2 - y1;
return Math.sqrt(dx * dx + dy * dy);
};
/**
* Calculer la distance euclidienne au carré (plus rapide si on n'a pas besoin de la racine)
*/
export const distanceSquared2D = (x1: number, y1: number, x2: number, y2: number): number => {
const dx = x2 - x1;
const dy = y2 - y1;
return dx * dx + dy * dy;
};
/**
* Normaliser un angle en radians entre 0 et 2π
*/
export const normalizeAngleRadians = (angle: number): number => {
while (angle < 0) angle += 2 * Math.PI;
while (angle >= 2 * Math.PI) angle -= 2 * Math.PI;
return angle;
};
/**
* Normaliser un angle en degrés entre 0 et 360
*/
export const normalizeAngleDegrees = (angle: number): number => {
while (angle < 0) angle += 360;
while (angle >= 360) angle -= 360;
return angle;
};
/**
* Calculer la différence entre deux angles (résultat entre -π et π)
*/
export const angleDifference = (angle1: number, angle2: number): number => {
const diff = angle2 - angle1;
return Math.atan2(Math.sin(diff), Math.cos(diff));
};
/**
* Interpolation d'angle (prend le chemin le plus court)
*/
export const lerpAngle = (start: number, end: number, progress: number): number => {
const diff = angleDifference(start, end);
return start + diff * progress;
};
/**
* Vérifier si un point est dans un rectangle
*/
export const isPointInRect = (
pointX: number,
pointY: number,
rectX: number,
rectY: number,
rectWidth: number,
rectHeight: number
): boolean => {
return (
pointX >= rectX &&
pointX <= rectX + rectWidth &&
pointY >= rectY &&
pointY <= rectY + rectHeight
);
};
/**
* Vérifier si un point est dans un cercle
*/
export const isPointInCircle = (
pointX: number,
pointY: number,
circleX: number,
circleY: number,
radius: number
): boolean => {
return distanceSquared2D(pointX, pointY, circleX, circleY) <= radius * radius;
};
/**
* Calculer l'aire d'un polygone (formule du lacet)
*/
export const polygonArea = (vertices: [number, number][]): number => {
if (vertices.length < 3) return 0;
let area = 0;
const n = vertices.length;
for (let i = 0; i < n; i++) {
const j = (i + 1) % n;
area += vertices[i][0] * vertices[j][1];
area -= vertices[j][0] * vertices[i][1];
}
return Math.abs(area) / 2;
};
/**
* Vérifier si un point est dans un polygone (ray casting algorithm)
*/
export const isPointInPolygon = (
point: [number, number],
vertices: [number, number][]
): boolean => {
const [x, y] = point;
let inside = false;
for (let i = 0, j = vertices.length - 1; i < vertices.length; j = i++) {
const [xi, yi] = vertices[i];
const [xj, yj] = vertices[j];
if (((yi > y) !== (yj > y)) && (x < (xj - xi) * (y - yi) / (yj - yi) + xi)) {
inside = !inside;
}
}
return inside;
};
/**
* Fonction easing pour les animations
*/
export const easing = {
linear: (t: number): number => t,
easeInQuad: (t: number): number => t * t,
easeOutQuad: (t: number): number => t * (2 - t),
easeInOutQuad: (t: number): number => t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t,
easeInCubic: (t: number): number => t * t * t,
easeOutCubic: (t: number): number => (--t) * t * t + 1,
easeInOutCubic: (t: number): number =>
t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1,
easeInSine: (t: number): number => 1 - Math.cos(t * Math.PI / 2),
easeOutSine: (t: number): number => Math.sin(t * Math.PI / 2),
easeInOutSine: (t: number): number => -(Math.cos(Math.PI * t) - 1) / 2,
};
/**
* Générer un hash simple pour un nombre (utile pour les seeds)
*/
export const simpleHash = (value: number): number => {
let hash = Math.abs(Math.floor(value));
hash = ((hash << 5) - hash + hash) & 0xffffffff;
return hash;
};
/**
* Générer un nombre pseudo-aléatoire avec une seed
*/
export const seededRandom = (seed: number): number => {
const x = Math.sin(seed) * 10000;
return x - Math.floor(x);
};
/**
* Calculer le facteur de zoom optimal pour afficher une zone
*/
export const calculateOptimalZoom = (
bounds: { width: number; height: number },
containerSize: { width: number; height: number },
maxZoom: number = 18,
padding: number = 0.1
): number => {
const paddedWidth = containerSize.width * (1 - padding);
const paddedHeight = containerSize.height * (1 - padding);
const widthZoom = Math.log2(paddedWidth / bounds.width);
const heightZoom = Math.log2(paddedHeight / bounds.height);
return Math.min(Math.floor(Math.min(widthZoom, heightZoom)), maxZoom);
};
/**
* Calculer le niveau de détail (LOD) basé sur la distance
*/
export const calculateLOD = (distance: number, maxDistance: number, maxLOD: number = 5): number => {
if (distance >= maxDistance) return 0;
const normalizedDistance = 1 - (distance / maxDistance);
return Math.floor(normalizedDistance * maxLOD);
};
/**
* Throttle pour limiter la fréquence d'exécution d'une fonction
*/
export const throttle = <T extends (...args: any[]) => any>(
func: T,
delay: number
): ((...args: Parameters<T>) => void) => {
let lastCall = 0;
return (...args: Parameters<T>) => {
const now = Date.now();
if (now - lastCall >= delay) {
lastCall = now;
func(...args);
}
};
};
/**
* Debounce pour retarder l'exécution d'une fonction
*/
export const debounce = <T extends (...args: any[]) => any>(
func: T,
delay: number
): ((...args: Parameters<T>) => void) => {
let timeoutId: ReturnType<typeof setTimeout>;
return (...args: Parameters<T>) => {
clearTimeout(timeoutId);
timeoutId = setTimeout(() => func(...args), delay);
};
};