# snoise.py - 极简噪声生成器
# 基于控制点的距离平方反比插值

class snoise:
    """简单噪声场 - 由控制点定义的平滑插值"""
    
    def __init__(self, falloff=1.0):
        """
        初始化噪声场
        
        参数:
            falloff: 衰减系数，默认1.0（越大衰减越快）
        """
        self.points = []  # [(x, y, value), ...]
        self.falloff = falloff
        self._cache = {}
    
    def add_point(self, x, y, value):
        """添加控制点"""
        self.points.append((float(x), float(y), float(value), self.falloff))
        self._cache.clear()  # 清空缓存
        return self
    
    def add_points(self, *points):
        """批量添加控制点
        snoise.add_points((0,0,1), (10,10,0), (5,5,0.5))
        """
        for p in points:
            if len(p) == 3:
                self.add_point(*p)
        return self
    
    def clear(self):
        """清空所有控制点"""
        self.points.clear()
        self._cache.clear()
        return self
    
    def set_falloff(self, falloff):
        """设置衰减系数"""
        self.falloff = falloff
        self._cache.clear()
        return self
    
    def _compute(self, x, y):
        """计算 (x,y) 处的噪声值"""
        if not self.points:
            return 0.0
        
        total_weight = 0.0
        total_value = 0.0
        
        for px, py, pv, pf in self.points:
            dx = x - px
            dy = y - py
            dist_sq = dx*dx + dy*dy
            
            if dist_sq == 0:
                return pv  # 正好在控制点上
            
            # 距离平方反比权重
            weight = 1.0 / (dist_sq ** (1/pf))
            total_weight += weight
            total_value += pv * weight
        
        return total_value
    
    def __call__(self, x, y, use_cache=True):
        """获取 (x,y) 处的噪声值"""
        if not use_cache:
            return self._compute(x, y)
        
        key = (float(x), float(y))
        if key not in self._cache:
            self._cache[key] = self._compute(x, y)
        return self._cache[key]
    
    def sample_rect(self, x1, y1, x2, y2, step=1.0):
        """采样矩形区域，返回二维数组"""
        width = int((x2 - x1) / step) + 1
        height = int((y2 - y1) / step) + 1
        
        result = []
        for iy in range(height):
            y = y1 + iy * step
            row = []
            for ix in range(width):
                x = x1 + ix * step
                row.append(self(x, y))
            result.append(row)
        return result
    
    def heatmap(self, x1, y1, x2, y2, step=1.0):
        """生成热力图数据（0-255整数）"""
        samples = self.sample_rect(x1, y1, x2, y2, step)
        
        # 找最小最大值
        flat = [v for row in samples for v in row]
        min_val = min(flat)
        max_val = max(flat)
        range_val = max_val - min_val or 1
        
        # 映射到0-255
        return [
            [int((v - min_val) / range_val * 255) for v in row]
            for row in samples
        ]
    
    def __repr__(self):
        return f"snoise(points={len(self.points)}, falloff={self.falloff})"


# ========== 快捷函数 ==========

def snoise_from_points(points, falloff=1.0):
    """从点列表创建噪声场"""
    n = snoise(falloff)
    n.add_points(*points)
    return n
