import serial
import math
import time

print("Connecting to /tmp/vtty1 to feed VGA interface...")
ser = serial.Serial('/tmp/vtty1', 115200)

# Clear the remote graphics server canvas
ser.write(b"!GRA\n")
ser.flush()
time.sleep(0.5)

GRID_SIZE = 22

# 1. Define a Sun Vector that strikes from an aggressive, low angle to force heavy shadows
SUN_VECTOR = [0.6, -0.6, 0.5]
s_len = math.sqrt(sum(v**2 for v in SUN_VECTOR))
SUN_VECTOR = [v / s_len for v in SUN_VECTOR]

# 1. Establish the maximum possible distance in your grid
# For a GRID_SIZE of 22, the furthest corner point is sqrt(22^2 + 22^2) ≈ 31.11
max_d = math.sqrt(2 * (GRID_SIZE ** 2))

def calculate_z(x, y):
    """Generates a sharper ripple wave to accentuate lighting changes"""
    d = math.sqrt(x**2 + y**2)

    # 2. Create a linear decay factor that drops from 1.0 (at center) to 0.0 (at edges)
    decay = max(0.0, 1.0 - (d / max_d))

    # 3. Multiply the base sine wave by the decay factor
    return math.cos(d * 0.4) * 9 * decay



def raw_isometric_projection(x, y, z):
    return (x - y) * 0.866, (x + y) * 0.500 - z

print("Processing surface geometry and computing light reflections...")
raw_facets = []
min_x, max_x = float('inf'), float('-inf')
min_y, max_y = float('inf'), float('-inf')

for x in range(-GRID_SIZE, GRID_SIZE - 1):
    for y in range(-GRID_SIZE, GRID_SIZE - 1):
        z00 = calculate_z(x, y)
        z10 = calculate_z(x + 1, y)
        z01 = calculate_z(x, y + 1)
        z11 = calculate_z(x + 1, y + 1)
        
        # 2. Strict Mathematical Surface Normal (Cross product of triangle edges)
        # Vector 1 (along X): (1, 0, z10 - z00)
        # Vector 2 (along Y): (0, 1, z01 - z00)
        nx = z00 - z10
        ny = z00 - z01
        nz = 1.0
        
        # Normalize the normal vector
        n_len = math.sqrt(nx**2 + ny**2 + nz**2)
        if n_len > 0: 
            nx, ny, nz = nx/n_len, ny/n_len, nz/n_len
            
        # 3. Vector dot product calculation
        dot_product = nx * SUN_VECTOR[0] + ny * SUN_VECTOR[1] + nz * SUN_VECTOR[2]
        
        # Clamp dot product so negative values (facing away from light) become 0.0
        intensity = max(0.0, dot_product)
        
        # --- AMBIENT LIGHT OVERRIDE ---
        # Compress the range so it scales between 0.30 (Ambient Min) and 1.00 (Max Peak)
        # Formula: Min_Ambient + (Remaining_Range * Intensity)
        shaded_brightness = 0.30 + (0.70 * intensity)
        
        # 4. Map the adjusted brightness to the VGA Grayscale Ramp (Indices 16 to 31)
        # Fully shadowed faces now safely land on index 20 (a visible medium-dark gray)
        vga_color_index = str(16 + int(shaded_brightness * 15.99))
        
        p00 = raw_isometric_projection(x, y, z00)
        p10 = raw_isometric_projection(x + 1, y, z10)
        p11 = raw_isometric_projection(x + 1, y + 1, z11)
        p01 = raw_isometric_projection(x, y + 1, z01)
        
        for px, py in [p00, p10, p11, p01]:
            if px < min_x: min_x = px
            if px > max_x: max_x = px
            if py < min_y: min_y = py
            if py > max_y: max_y = py

        raw_facets.append({
            'depth': x + y,
            'color_idx': vga_color_index,
            'raw_points': [p00, p10, p11, p01]
        })

# Sizing adjustments mapping cleanly onto the 1024x768 server coordinate rules
raw_width, raw_height = max_x - min_x, max_y - min_y
scale_factor = min((1024 - 80) / raw_width, (768 - 80) / raw_height)
offset_x = 512 - (raw_width * scale_factor) / 2.0 - (min_x * scale_factor)
offset_y = 384 - (raw_height * scale_factor) / 2.0 - (min_y * scale_factor)

raw_facets.sort(key=lambda f: f['depth'])

print("Streaming layout coordinates to terminal window layer...")
for index, f in enumerate(raw_facets):
    scr_points = []
    for rx, ry in f['raw_points']:
        sx = int(rx * scale_factor + offset_x)
        sy = int(ry * scale_factor + offset_y)
        scr_points.append(f"{sx} {sy}")
    
    # Structure text packet syntax string: !POL [color] [x1] [y1] [x2] [y2] ...
    coords_str = " ".join(scr_points)
    packet = f"!POL {f['color_idx']} {coords_str}\n"
    
    ser.write(packet.encode())
    ser.flush()
    
    if index % 12 == 0:
        time.sleep(0.01)

print("Shaded VGA Application Surface Complete!")
ser.close()

