1. Canvas坐标系统基础

1.1 坐标系统概念

Canvas使用二维笛卡尔坐标系统,原点(0,0)位于左上角:

// 获取canvas和上下文
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

// Canvas坐标系统特点:
// - 原点(0,0)在左上角
// - X轴向右为正方向
// - Y轴向下为正方向
// - 单位为像素

// 绘制坐标系参考线
function drawCoordinateSystem() {
    const width = canvas.width;
    const height = canvas.height;
    
    ctx.strokeStyle = 'lightgray';
    ctx.lineWidth = 1;
    
    // 绘制网格
    for (let x = 0; x <= width; x += 50) {
        ctx.beginPath();
        ctx.moveTo(x, 0);
        ctx.lineTo(x, height);
        ctx.stroke();
    }
    
    for (let y = 0; y <= height; y += 50) {
        ctx.beginPath();
        ctx.moveTo(0, y);
        ctx.lineTo(width, y);
        ctx.stroke();
    }
    
    // 绘制坐标轴
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2;
    
    // X轴
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(width, 0);
    ctx.stroke();
    
    // Y轴
    ctx.beginPath();
    ctx.moveTo(0, 0);
    ctx.lineTo(0, height);
    ctx.stroke();
    
    // 标记原点
    ctx.fillStyle = 'red';
    ctx.fillRect(-2, -2, 4, 4);
    
    // 添加坐标标签
    ctx.fillStyle = 'black';
    ctx.font = '12px Arial';
    ctx.fillText('(0,0)', 5, 15);
    ctx.fillText(`(${width},0)`, width - 50, 15);
    ctx.fillText(`(0,${height})`, 5, height - 5);
}

drawCoordinateSystem();

1.2 坐标转换工具

// 坐标转换工具类
class CoordinateUtils {
    // 屏幕坐标转Canvas坐标
    static screenToCanvas(canvas, screenX, screenY) {
        const rect = canvas.getBoundingClientRect();
        return {
            x: screenX - rect.left,
            y: screenY - rect.top
        };
    }
    
    // Canvas坐标转屏幕坐标
    static canvasToScreen(canvas, canvasX, canvasY) {
        const rect = canvas.getBoundingClientRect();
        return {
            x: canvasX + rect.left,
            y: canvasY + rect.top
        };
    }
    
    // 数学坐标系转Canvas坐标系
    static mathToCanvas(mathX, mathY, centerX, centerY, scale = 1) {
        return {
            x: centerX + mathX * scale,
            y: centerY - mathY * scale  // 注意Y轴翻转
        };
    }
    
    // Canvas坐标系转数学坐标系
    static canvasToMath(canvasX, canvasY, centerX, centerY, scale = 1) {
        return {
            x: (canvasX - centerX) / scale,
            y: (centerY - canvasY) / scale  // 注意Y轴翻转
        };
    }
    
    // 极坐标转直角坐标
    static polarToCartesian(r, theta) {
        return {
            x: r * Math.cos(theta),
            y: r * Math.sin(theta)
        };
    }
    
    // 直角坐标转极坐标
    static cartesianToPolar(x, y) {
        return {
            r: Math.sqrt(x * x + y * y),
            theta: Math.atan2(y, x)
        };
    }
}

// 使用示例
canvas.addEventListener('mousemove', (e) => {
    const canvasCoord = CoordinateUtils.screenToCanvas(canvas, e.clientX, e.clientY);
    const mathCoord = CoordinateUtils.canvasToMath(
        canvasCoord.x, canvasCoord.y,
        canvas.width / 2, canvas.height / 2, 1
    );
    
    console.log(`Canvas: (${canvasCoord.x.toFixed(1)}, ${canvasCoord.y.toFixed(1)})`);
    console.log(`Math: (${mathCoord.x.toFixed(1)}, ${mathCoord.y.toFixed(1)})`);
});

2. 基础变换操作

2.1 平移变换 (translate)

// 平移变换基础
function translateDemo() {
    // 保存当前状态
    ctx.save();
    
    // 绘制原始矩形
    ctx.fillStyle = 'red';
    ctx.fillRect(50, 50, 100, 50);
    
    // 平移坐标系
    ctx.translate(150, 100);
    
    // 在新坐标系中绘制(相对于新原点)
    ctx.fillStyle = 'blue';
    ctx.fillRect(0, 0, 100, 50);  // 实际位置是 (200, 150)
    
    // 再次平移
    ctx.translate(50, 50);
    ctx.fillStyle = 'green';
    ctx.fillRect(0, 0, 100, 50);  // 实际位置是 (250, 200)
    
    // 恢复状态
    ctx.restore();
    
    // 验证坐标系已恢复
    ctx.fillStyle = 'orange';
    ctx.fillRect(50, 200, 100, 50);
}

translateDemo();

2.2 旋转变换 (rotate)

// 旋转变换基础
function rotateDemo() {
    const centerX = 400;
    const centerY = 150;
    
    // 绘制旋转中心点
    ctx.fillStyle = 'red';
    ctx.fillRect(centerX - 2, centerY - 2, 4, 4);
    
    // 绘制多个旋转的矩形
    for (let i = 0; i < 8; i++) {
        ctx.save();
        
        // 移动到旋转中心
        ctx.translate(centerX, centerY);
        
        // 旋转
        ctx.rotate((i * Math.PI) / 4);
        
        // 绘制矩形(相对于旋转中心)
        ctx.fillStyle = `hsl(${i * 45}, 70%, 50%)`;
        ctx.fillRect(-50, -10, 100, 20);
        
        ctx.restore();
    }
}

rotateDemo();

2.3 缩放变换 (scale)

// 缩放变换基础
function scaleDemo() {
    const baseX = 600;
    const baseY = 50;
    
    // 原始大小
    ctx.fillStyle = 'red';
    ctx.fillRect(baseX, baseY, 50, 50);
    
    // 不同缩放比例
    const scales = [0.5, 1.5, 2.0, 2.5];
    
    scales.forEach((scale, index) => {
        ctx.save();
        
        const x = baseX + (index + 1) * 80;
        
        // 移动到缩放中心
        ctx.translate(x + 25, baseY + 25);
        
        // 缩放
        ctx.scale(scale, scale);
        
        // 绘制矩形(以中心为原点)
        ctx.fillStyle = `hsl(${index * 60}, 70%, 50%)`;
        ctx.fillRect(-25, -25, 50, 50);
        
        ctx.restore();
        
        // 添加标签
        ctx.fillStyle = 'black';
        ctx.font = '12px Arial';
        ctx.fillText(`${scale}x`, x + 10, baseY + 70);
    });
}

scaleDemo();

2.4 组合变换

// 组合变换示例
function combinedTransformDemo() {
    const centerX = 400;
    const centerY = 350;
    
    // 绘制参考点
    ctx.fillStyle = 'red';
    ctx.fillRect(centerX - 2, centerY - 2, 4, 4);
    
    // 创建一个复杂的变换序列
    ctx.save();
    
    // 1. 平移到中心点
    ctx.translate(centerX, centerY);
    
    // 2. 旋转45度
    ctx.rotate(Math.PI / 4);
    
    // 3. 缩放1.5倍
    ctx.scale(1.5, 1.5);
    
    // 4. 再次平移
    ctx.translate(30, 0);
    
    // 绘制图形
    ctx.fillStyle = 'blue';
    ctx.fillRect(-25, -25, 50, 50);
    
    // 在变换后的坐标系中继续绘制
    ctx.fillStyle = 'green';
    ctx.beginPath();
    ctx.arc(0, 0, 15, 0, Math.PI * 2);
    ctx.fill();
    
    ctx.restore();
}

combinedTransformDemo();

3. 变换矩阵

3.1 变换矩阵基础

// 变换矩阵类
class TransformMatrix {
    constructor(a = 1, b = 0, c = 0, d = 1, e = 0, f = 0) {
        // 矩阵格式:
        // [a c e]
        // [b d f]
        // [0 0 1]
        this.a = a;  // 水平缩放
        this.b = b;  // 水平倾斜
        this.c = c;  // 垂直倾斜
        this.d = d;  // 垂直缩放
        this.e = e;  // 水平平移
        this.f = f;  // 垂直平移
    }
    
    // 创建单位矩阵
    static identity() {
        return new TransformMatrix();
    }
    
    // 创建平移矩阵
    static translate(tx, ty) {
        return new TransformMatrix(1, 0, 0, 1, tx, ty);
    }
    
    // 创建旋转矩阵
    static rotate(angle) {
        const cos = Math.cos(angle);
        const sin = Math.sin(angle);
        return new TransformMatrix(cos, sin, -sin, cos, 0, 0);
    }
    
    // 创建缩放矩阵
    static scale(sx, sy = sx) {
        return new TransformMatrix(sx, 0, 0, sy, 0, 0);
    }
    
    // 创建倾斜矩阵
    static skew(skewX, skewY) {
        return new TransformMatrix(
            1, Math.tan(skewY),
            Math.tan(skewX), 1,
            0, 0
        );
    }
    
    // 矩阵乘法
    multiply(other) {
        const a = this.a * other.a + this.c * other.b;
        const b = this.b * other.a + this.d * other.b;
        const c = this.a * other.c + this.c * other.d;
        const d = this.b * other.c + this.d * other.d;
        const e = this.a * other.e + this.c * other.f + this.e;
        const f = this.b * other.e + this.d * other.f + this.f;
        
        return new TransformMatrix(a, b, c, d, e, f);
    }
    
    // 变换点
    transformPoint(x, y) {
        return {
            x: this.a * x + this.c * y + this.e,
            y: this.b * x + this.d * y + this.f
        };
    }
    
    // 获取逆矩阵
    inverse() {
        const det = this.a * this.d - this.b * this.c;
        
        if (Math.abs(det) < 1e-10) {
            throw new Error('Matrix is not invertible');
        }
        
        return new TransformMatrix(
            this.d / det,
            -this.b / det,
            -this.c / det,
            this.a / det,
            (this.c * this.f - this.d * this.e) / det,
            (this.b * this.e - this.a * this.f) / det
        );
    }
    
    // 应用到Canvas上下文
    applyToContext(ctx) {
        ctx.setTransform(this.a, this.b, this.c, this.d, this.e, this.f);
    }
    
    // 转换为CSS transform字符串
    toCSSTransform() {
        return `matrix(${this.a}, ${this.b}, ${this.c}, ${this.d}, ${this.e}, ${this.f})`;
    }
    
    // 克隆矩阵
    clone() {
        return new TransformMatrix(this.a, this.b, this.c, this.d, this.e, this.f);
    }
}

// 使用变换矩阵
function matrixDemo() {
    // 创建复合变换矩阵
    const transform = TransformMatrix.identity()
        .multiply(TransformMatrix.translate(200, 200))
        .multiply(TransformMatrix.rotate(Math.PI / 6))
        .multiply(TransformMatrix.scale(1.5, 1.0))
        .multiply(TransformMatrix.translate(-25, -25));
    
    // 应用变换
    transform.applyToContext(ctx);
    
    // 绘制图形
    ctx.fillStyle = 'purple';
    ctx.fillRect(0, 0, 50, 50);
    
    // 重置变换
    ctx.setTransform(1, 0, 0, 1, 0, 0);
    
    // 验证点变换
    const originalPoint = { x: 25, y: 25 };
    const transformedPoint = transform.transformPoint(originalPoint.x, originalPoint.y);
    
    console.log('原始点:', originalPoint);
    console.log('变换后点:', transformedPoint);
    
    // 绘制变换后的点
    ctx.fillStyle = 'red';
    ctx.fillRect(transformedPoint.x - 2, transformedPoint.y - 2, 4, 4);
}

matrixDemo();

3.2 自定义变换

// 自定义变换效果
class CustomTransforms {
    // 透视变换
    static perspective(ctx, focalLength, vanishingPointX, vanishingPointY) {
        return {
            transformPoint(x, y, z = 0) {
                const scale = focalLength / (focalLength + z);
                return {
                    x: vanishingPointX + (x - vanishingPointX) * scale,
                    y: vanishingPointY + (y - vanishingPointY) * scale
                };
            }
        };
    }
    
    // 弹性变换
    static elastic(amplitude, frequency, phase = 0) {
        return {
            transformPoint(x, y) {
                const offset = Math.sin(x * frequency + phase) * amplitude;
                return { x, y: y + offset };
            }
        };
    }
    
    // 波浪变换
    static wave(amplitude, wavelength, direction = 'horizontal') {
        return {
            transformPoint(x, y) {
                if (direction === 'horizontal') {
                    const offset = Math.sin((x / wavelength) * Math.PI * 2) * amplitude;
                    return { x, y: y + offset };
                } else {
                    const offset = Math.sin((y / wavelength) * Math.PI * 2) * amplitude;
                    return { x: x + offset, y };
                }
            }
        };
    }
    
    // 漩涡变换
    static vortex(centerX, centerY, strength) {
        return {
            transformPoint(x, y) {
                const dx = x - centerX;
                const dy = y - centerY;
                const distance = Math.sqrt(dx * dx + dy * dy);
                const angle = Math.atan2(dy, dx) + (strength / (distance + 1));
                
                return {
                    x: centerX + distance * Math.cos(angle),
                    y: centerY + distance * Math.sin(angle)
                };
            }
        };
    }
}

// 应用自定义变换
function customTransformDemo() {
    // 创建网格进行变换演示
    function drawTransformedGrid(transform, startX, startY, gridSize = 20, gridCount = 10) {
        ctx.strokeStyle = 'blue';
        ctx.lineWidth = 1;
        
        // 绘制水平线
        for (let i = 0; i <= gridCount; i++) {
            ctx.beginPath();
            for (let j = 0; j <= gridCount; j++) {
                const x = startX + j * gridSize;
                const y = startY + i * gridSize;
                const transformed = transform.transformPoint(x, y);
                
                if (j === 0) {
                    ctx.moveTo(transformed.x, transformed.y);
                } else {
                    ctx.lineTo(transformed.x, transformed.y);
                }
            }
            ctx.stroke();
        }
        
        // 绘制垂直线
        for (let j = 0; j <= gridCount; j++) {
            ctx.beginPath();
            for (let i = 0; i <= gridCount; i++) {
                const x = startX + j * gridSize;
                const y = startY + i * gridSize;
                const transformed = transform.transformPoint(x, y);
                
                if (i === 0) {
                    ctx.moveTo(transformed.x, transformed.y);
                } else {
                    ctx.lineTo(transformed.x, transformed.y);
                }
            }
            ctx.stroke();
        }
    }
    
    // 波浪变换
    const waveTransform = CustomTransforms.wave(20, 100, 'horizontal');
    drawTransformedGrid(waveTransform, 50, 400, 15, 8);
    
    // 漩涡变换
    const vortexTransform = CustomTransforms.vortex(400, 500, 0.5);
    drawTransformedGrid(vortexTransform, 300, 400, 15, 8);
    
    // 弹性变换
    const elasticTransform = CustomTransforms.elastic(15, 0.1, 0);
    drawTransformedGrid(elasticTransform, 550, 400, 15, 8);
}

customTransformDemo();

4. 坐标系统管理

4.1 状态栈管理

// 高级状态管理类
class CanvasStateManager {
    constructor(ctx) {
        this.ctx = ctx;
        this.stateStack = [];
        this.namedStates = new Map();
    }
    
    // 保存当前状态
    save(name = null) {
        const state = this.getCurrentState();
        this.stateStack.push(state);
        
        if (name) {
            this.namedStates.set(name, state);
        }
        
        this.ctx.save();
        return this;
    }
    
    // 恢复状态
    restore() {
        if (this.stateStack.length > 0) {
            this.stateStack.pop();
        }
        this.ctx.restore();
        return this;
    }
    
    // 恢复到命名状态
    restoreToNamed(name) {
        if (this.namedStates.has(name)) {
            const state = this.namedStates.get(name);
            this.applyState(state);
        }
        return this;
    }
    
    // 获取当前状态
    getCurrentState() {
        const transform = this.ctx.getTransform();
        return {
            transform: {
                a: transform.a,
                b: transform.b,
                c: transform.c,
                d: transform.d,
                e: transform.e,
                f: transform.f
            },
            fillStyle: this.ctx.fillStyle,
            strokeStyle: this.ctx.strokeStyle,
            lineWidth: this.ctx.lineWidth,
            lineCap: this.ctx.lineCap,
            lineJoin: this.ctx.lineJoin,
            globalAlpha: this.ctx.globalAlpha,
            globalCompositeOperation: this.ctx.globalCompositeOperation
        };
    }
    
    // 应用状态
    applyState(state) {
        this.ctx.setTransform(
            state.transform.a,
            state.transform.b,
            state.transform.c,
            state.transform.d,
            state.transform.e,
            state.transform.f
        );
        
        this.ctx.fillStyle = state.fillStyle;
        this.ctx.strokeStyle = state.strokeStyle;
        this.ctx.lineWidth = state.lineWidth;
        this.ctx.lineCap = state.lineCap;
        this.ctx.lineJoin = state.lineJoin;
        this.ctx.globalAlpha = state.globalAlpha;
        this.ctx.globalCompositeOperation = state.globalCompositeOperation;
    }
    
    // 重置到初始状态
    reset() {
        this.ctx.setTransform(1, 0, 0, 1, 0, 0);
        this.ctx.fillStyle = 'black';
        this.ctx.strokeStyle = 'black';
        this.ctx.lineWidth = 1;
        this.ctx.lineCap = 'butt';
        this.ctx.lineJoin = 'miter';
        this.ctx.globalAlpha = 1;
        this.ctx.globalCompositeOperation = 'source-over';
        return this;
    }
    
    // 链式变换方法
    translate(x, y) {
        this.ctx.translate(x, y);
        return this;
    }
    
    rotate(angle) {
        this.ctx.rotate(angle);
        return this;
    }
    
    scale(x, y = x) {
        this.ctx.scale(x, y);
        return this;
    }
    
    // 执行带状态保护的操作
    withState(callback, name = null) {
        this.save(name);
        try {
            callback(this.ctx);
        } finally {
            this.restore();
        }
        return this;
    }
}

// 使用状态管理器
const stateManager = new CanvasStateManager(ctx);

// 链式操作示例
stateManager
    .save('initial')
    .translate(100, 100)
    .rotate(Math.PI / 4)
    .scale(1.5)
    .withState((ctx) => {
        ctx.fillStyle = 'red';
        ctx.fillRect(-25, -25, 50, 50);
    })
    .translate(100, 0)
    .withState((ctx) => {
        ctx.fillStyle = 'blue';
        ctx.fillRect(-25, -25, 50, 50);
    })
    .restoreToNamed('initial');

4.2 视口和相机系统

// 2D相机系统
class Camera2D {
    constructor(x = 0, y = 0, zoom = 1, rotation = 0) {
        this.x = x;
        this.y = y;
        this.zoom = zoom;
        this.rotation = rotation;
        this.bounds = null;
    }
    
    // 设置边界限制
    setBounds(minX, minY, maxX, maxY) {
        this.bounds = { minX, minY, maxX, maxY };
    }
    
    // 移动相机
    move(dx, dy) {
        this.x += dx;
        this.y += dy;
        this.constrainToBounds();
    }
    
    // 设置相机位置
    setPosition(x, y) {
        this.x = x;
        this.y = y;
        this.constrainToBounds();
    }
    
    // 缩放
    setZoom(zoom) {
        this.zoom = Math.max(0.1, Math.min(10, zoom));
    }
    
    // 旋转
    setRotation(rotation) {
        this.rotation = rotation;
    }
    
    // 约束到边界
    constrainToBounds() {
        if (this.bounds) {
            this.x = Math.max(this.bounds.minX, Math.min(this.bounds.maxX, this.x));
            this.y = Math.max(this.bounds.minY, Math.min(this.bounds.maxY, this.y));
        }
    }
    
    // 应用相机变换
    applyTransform(ctx, canvasWidth, canvasHeight) {
        // 移动到画布中心
        ctx.translate(canvasWidth / 2, canvasHeight / 2);
        
        // 应用缩放
        ctx.scale(this.zoom, this.zoom);
        
        // 应用旋转
        ctx.rotate(this.rotation);
        
        // 应用相机位置(注意符号)
        ctx.translate(-this.x, -this.y);
    }
    
    // 屏幕坐标转世界坐标
    screenToWorld(screenX, screenY, canvasWidth, canvasHeight) {
        // 转换到以画布中心为原点的坐标
        const centerX = screenX - canvasWidth / 2;
        const centerY = screenY - canvasHeight / 2;
        
        // 应用逆变换
        const cos = Math.cos(-this.rotation);
        const sin = Math.sin(-this.rotation);
        
        const rotatedX = centerX * cos - centerY * sin;
        const rotatedY = centerX * sin + centerY * cos;
        
        return {
            x: this.x + rotatedX / this.zoom,
            y: this.y + rotatedY / this.zoom
        };
    }
    
    // 世界坐标转屏幕坐标
    worldToScreen(worldX, worldY, canvasWidth, canvasHeight) {
        // 相对于相机的位置
        const relativeX = (worldX - this.x) * this.zoom;
        const relativeY = (worldY - this.y) * this.zoom;
        
        // 应用旋转
        const cos = Math.cos(this.rotation);
        const sin = Math.sin(this.rotation);
        
        const rotatedX = relativeX * cos - relativeY * sin;
        const rotatedY = relativeX * sin + relativeY * cos;
        
        return {
            x: rotatedX + canvasWidth / 2,
            y: rotatedY + canvasHeight / 2
        };
    }
    
    // 跟随目标
    followTarget(targetX, targetY, lerp = 0.1) {
        this.x += (targetX - this.x) * lerp;
        this.y += (targetY - this.y) * lerp;
        this.constrainToBounds();
    }
    
    // 获取可视区域
    getViewBounds(canvasWidth, canvasHeight) {
        const halfWidth = (canvasWidth / 2) / this.zoom;
        const halfHeight = (canvasHeight / 2) / this.zoom;
        
        return {
            left: this.x - halfWidth,
            right: this.x + halfWidth,
            top: this.y - halfHeight,
            bottom: this.y + halfHeight
        };
    }
}

// 相机使用示例
const camera = new Camera2D(0, 0, 1, 0);
camera.setBounds(-500, -500, 500, 500);

// 世界对象
const worldObjects = [
    { x: 0, y: 0, width: 50, height: 50, color: 'red' },
    { x: 100, y: 100, width: 30, height: 30, color: 'blue' },
    { x: -150, y: 50, width: 40, height: 40, color: 'green' },
    { x: 200, y: -100, width: 60, height: 20, color: 'yellow' }
];

// 渲染函数
function renderWithCamera() {
    // 清除画布
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 保存状态
    ctx.save();
    
    // 应用相机变换
    camera.applyTransform(ctx, canvas.width, canvas.height);
    
    // 绘制世界坐标系
    ctx.strokeStyle = 'lightgray';
    ctx.lineWidth = 1 / camera.zoom;  // 保持线条粗细
    
    // 绘制网格
    const bounds = camera.getViewBounds(canvas.width, canvas.height);
    const gridSize = 50;
    
    for (let x = Math.floor(bounds.left / gridSize) * gridSize; x <= bounds.right; x += gridSize) {
        ctx.beginPath();
        ctx.moveTo(x, bounds.top);
        ctx.lineTo(x, bounds.bottom);
        ctx.stroke();
    }
    
    for (let y = Math.floor(bounds.top / gridSize) * gridSize; y <= bounds.bottom; y += gridSize) {
        ctx.beginPath();
        ctx.moveTo(bounds.left, y);
        ctx.lineTo(bounds.right, y);
        ctx.stroke();
    }
    
    // 绘制坐标轴
    ctx.strokeStyle = 'black';
    ctx.lineWidth = 2 / camera.zoom;
    
    ctx.beginPath();
    ctx.moveTo(bounds.left, 0);
    ctx.lineTo(bounds.right, 0);
    ctx.stroke();
    
    ctx.beginPath();
    ctx.moveTo(0, bounds.top);
    ctx.lineTo(0, bounds.bottom);
    ctx.stroke();
    
    // 绘制世界对象
    worldObjects.forEach(obj => {
        ctx.fillStyle = obj.color;
        ctx.fillRect(obj.x - obj.width/2, obj.y - obj.height/2, obj.width, obj.height);
    });
    
    // 恢复状态
    ctx.restore();
    
    // 绘制UI(不受相机影响)
    ctx.fillStyle = 'black';
    ctx.font = '14px Arial';
    ctx.fillText(`Camera: (${camera.x.toFixed(1)}, ${camera.y.toFixed(1)})`, 10, 20);
    ctx.fillText(`Zoom: ${camera.zoom.toFixed(2)}x`, 10, 40);
    ctx.fillText(`Rotation: ${(camera.rotation * 180 / Math.PI).toFixed(1)}°`, 10, 60);
}

// 相机控制
let keys = {};

window.addEventListener('keydown', (e) => {
    keys[e.key] = true;
});

window.addEventListener('keyup', (e) => {
    keys[e.key] = false;
});

canvas.addEventListener('wheel', (e) => {
    e.preventDefault();
    const zoomFactor = e.deltaY > 0 ? 0.9 : 1.1;
    camera.setZoom(camera.zoom * zoomFactor);
});

// 相机更新循环
function updateCamera() {
    const speed = 5 / camera.zoom;
    
    if (keys['ArrowLeft'] || keys['a']) camera.move(-speed, 0);
    if (keys['ArrowRight'] || keys['d']) camera.move(speed, 0);
    if (keys['ArrowUp'] || keys['w']) camera.move(0, -speed);
    if (keys['ArrowDown'] || keys['s']) camera.move(0, speed);
    
    if (keys['q']) camera.setRotation(camera.rotation - 0.02);
    if (keys['e']) camera.setRotation(camera.rotation + 0.02);
    
    renderWithCamera();
    requestAnimationFrame(updateCamera);
}

// 开始相机循环
updateCamera();

5. 高级变换技巧

5.1 动画变换

// 动画变换类
class AnimatedTransform {
    constructor() {
        this.animations = [];
        this.isRunning = false;
    }
    
    // 添加动画
    addAnimation(config) {
        const animation = {
            id: Math.random().toString(36).substr(2, 9),
            startTime: performance.now(),
            duration: config.duration || 1000,
            easing: config.easing || this.easeInOutQuad,
            from: config.from,
            to: config.to,
            onUpdate: config.onUpdate,
            onComplete: config.onComplete
        };
        
        this.animations.push(animation);
        
        if (!this.isRunning) {
            this.start();
        }
        
        return animation.id;
    }
    
    // 移除动画
    removeAnimation(id) {
        this.animations = this.animations.filter(anim => anim.id !== id);
    }
    
    // 开始动画循环
    start() {
        this.isRunning = true;
        this.update();
    }
    
    // 停止动画循环
    stop() {
        this.isRunning = false;
    }
    
    // 更新动画
    update() {
        if (!this.isRunning) return;
        
        const currentTime = performance.now();
        
        this.animations = this.animations.filter(animation => {
            const elapsed = currentTime - animation.startTime;
            const progress = Math.min(elapsed / animation.duration, 1);
            const easedProgress = animation.easing(progress);
            
            // 计算当前值
            const currentValue = this.interpolate(animation.from, animation.to, easedProgress);
            
            // 调用更新回调
            if (animation.onUpdate) {
                animation.onUpdate(currentValue, progress);
            }
            
            // 检查是否完成
            if (progress >= 1) {
                if (animation.onComplete) {
                    animation.onComplete(animation.to);
                }
                return false; // 移除动画
            }
            
            return true; // 保留动画
        });
        
        if (this.animations.length > 0) {
            requestAnimationFrame(() => this.update());
        } else {
            this.isRunning = false;
        }
    }
    
    // 插值函数
    interpolate(from, to, progress) {
        if (typeof from === 'number') {
            return from + (to - from) * progress;
        }
        
        if (typeof from === 'object') {
            const result = {};
            for (const key in from) {
                if (from.hasOwnProperty(key)) {
                    result[key] = this.interpolate(from[key], to[key], progress);
                }
            }
            return result;
        }
        
        return progress < 0.5 ? from : to;
    }
    
    // 缓动函数
    easeInOutQuad(t) {
        return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t;
    }
    
    easeInOutCubic(t) {
        return t < 0.5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1;
    }
    
    easeInOutElastic(t) {
        if (t === 0 || t === 1) return t;
        
        const p = 0.3;
        const s = p / 4;
        
        if (t < 0.5) {
            t = 2 * t - 1;
            return -0.5 * Math.pow(2, 10 * t) * Math.sin((t - s) * (2 * Math.PI) / p);
        } else {
            t = 2 * t - 1;
            return 0.5 * Math.pow(2, -10 * t) * Math.sin((t - s) * (2 * Math.PI) / p) + 1;
        }
    }
}

// 使用动画变换
const animator = new AnimatedTransform();

// 创建一个旋转的矩形
let rectTransform = {
    x: 400,
    y: 300,
    rotation: 0,
    scale: 1
};

// 添加旋转动画
animator.addAnimation({
    duration: 3000,
    from: { rotation: 0 },
    to: { rotation: Math.PI * 2 },
    easing: animator.easeInOutQuad,
    onUpdate: (value) => {
        rectTransform.rotation = value.rotation;
    },
    onComplete: () => {
        // 循环动画
        animator.addAnimation({
            duration: 3000,
            from: { rotation: 0 },
            to: { rotation: Math.PI * 2 },
            easing: animator.easeInOutQuad,
            onUpdate: (value) => {
                rectTransform.rotation = value.rotation;
            }
        });
    }
});

// 添加缩放动画
animator.addAnimation({
    duration: 2000,
    from: { scale: 1 },
    to: { scale: 1.5 },
    easing: animator.easeInOutElastic,
    onUpdate: (value) => {
        rectTransform.scale = value.scale;
    },
    onComplete: () => {
        // 反向缩放
        animator.addAnimation({
            duration: 2000,
            from: { scale: 1.5 },
            to: { scale: 1 },
            easing: animator.easeInOutElastic,
            onUpdate: (value) => {
                rectTransform.scale = value.scale;
            }
        });
    }
});

// 渲染动画
function renderAnimation() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    ctx.save();
    
    // 应用变换
    ctx.translate(rectTransform.x, rectTransform.y);
    ctx.rotate(rectTransform.rotation);
    ctx.scale(rectTransform.scale, rectTransform.scale);
    
    // 绘制矩形
    ctx.fillStyle = 'blue';
    ctx.fillRect(-25, -25, 50, 50);
    
    ctx.restore();
    
    requestAnimationFrame(renderAnimation);
}

renderAnimation();

5.2 3D效果模拟

// 伪3D变换类
class Pseudo3D {
    constructor(focalLength = 300) {
        this.focalLength = focalLength;
    }
    
    // 3D点投影到2D
    project(x, y, z) {
        const scale = this.focalLength / (this.focalLength + z);
        return {
            x: x * scale,
            y: y * scale,
            scale: scale
        };
    }
    
    // 绘制3D立方体
    drawCube(ctx, centerX, centerY, size, rotationX, rotationY, rotationZ) {
        // 立方体顶点(相对于中心)
        const vertices = [
            [-size, -size, -size], [size, -size, -size],
            [size, size, -size], [-size, size, -size],
            [-size, -size, size], [size, -size, size],
            [size, size, size], [-size, size, size]
        ];
        
        // 应用旋转
        const rotatedVertices = vertices.map(vertex => {
            return this.rotatePoint(vertex, rotationX, rotationY, rotationZ);
        });
        
        // 投影到2D
        const projectedVertices = rotatedVertices.map(vertex => {
            const projected = this.project(vertex[0], vertex[1], vertex[2]);
            return {
                x: centerX + projected.x,
                y: centerY + projected.y,
                z: vertex[2],
                scale: projected.scale
            };
        });
        
        // 定义面(顶点索引)
        const faces = [
            [0, 1, 2, 3], // 前面
            [4, 7, 6, 5], // 后面
            [0, 4, 5, 1], // 底面
            [2, 6, 7, 3], // 顶面
            [0, 3, 7, 4], // 左面
            [1, 5, 6, 2]  // 右面
        ];
        
        // 计算面的平均Z值用于排序
        const facesWithZ = faces.map((face, index) => {
            const avgZ = face.reduce((sum, vertexIndex) => {
                return sum + projectedVertices[vertexIndex].z;
            }, 0) / face.length;
            
            return { face, avgZ, index };
        });
        
        // 按Z值排序(远的先绘制)
        facesWithZ.sort((a, b) => a.avgZ - b.avgZ);
        
        // 绘制面
        facesWithZ.forEach(({ face, index }) => {
            ctx.beginPath();
            
            face.forEach((vertexIndex, i) => {
                const vertex = projectedVertices[vertexIndex];
                if (i === 0) {
                    ctx.moveTo(vertex.x, vertex.y);
                } else {
                    ctx.lineTo(vertex.x, vertex.y);
                }
            });
            
            ctx.closePath();
            
            // 根据面的索引设置颜色
            const colors = ['red', 'green', 'blue', 'yellow', 'orange', 'purple'];
            const brightness = Math.max(0.3, (facesWithZ[index].avgZ + 100) / 200);
            
            ctx.fillStyle = this.adjustBrightness(colors[index], brightness);
            ctx.fill();
            
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 1;
            ctx.stroke();
        });
    }
    
    // 旋转点
    rotatePoint([x, y, z], rotX, rotY, rotZ) {
        // 绕X轴旋转
        let cosX = Math.cos(rotX), sinX = Math.sin(rotX);
        let y1 = y * cosX - z * sinX;
        let z1 = y * sinX + z * cosX;
        
        // 绕Y轴旋转
        let cosY = Math.cos(rotY), sinY = Math.sin(rotY);
        let x2 = x * cosY + z1 * sinY;
        let z2 = -x * sinY + z1 * cosY;
        
        // 绕Z轴旋转
        let cosZ = Math.cos(rotZ), sinZ = Math.sin(rotZ);
        let x3 = x2 * cosZ - y1 * sinZ;
        let y3 = x2 * sinZ + y1 * cosZ;
        
        return [x3, y3, z2];
    }
    
    // 调整颜色亮度
    adjustBrightness(color, brightness) {
        const colors = {
            red: [255, 0, 0],
            green: [0, 255, 0],
            blue: [0, 0, 255],
            yellow: [255, 255, 0],
            orange: [255, 165, 0],
            purple: [128, 0, 128]
        };
        
        const rgb = colors[color] || [128, 128, 128];
        const adjustedRgb = rgb.map(c => Math.floor(c * brightness));
        
        return `rgb(${adjustedRgb[0]}, ${adjustedRgb[1]}, ${adjustedRgb[2]})`;
    }
}

// 3D演示
const pseudo3D = new Pseudo3D(400);
let cubeRotation = { x: 0, y: 0, z: 0 };

function render3D() {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    
    // 更新旋转
    cubeRotation.x += 0.01;
    cubeRotation.y += 0.015;
    cubeRotation.z += 0.005;
    
    // 绘制多个立方体
    const positions = [
        { x: 200, y: 200, size: 50 },
        { x: 400, y: 200, size: 30 },
        { x: 600, y: 200, size: 40 }
    ];
    
    positions.forEach((pos, index) => {
        const rotX = cubeRotation.x + index * 0.5;
        const rotY = cubeRotation.y + index * 0.3;
        const rotZ = cubeRotation.z + index * 0.7;
        
        pseudo3D.drawCube(ctx, pos.x, pos.y, pos.size, rotX, rotY, rotZ);
    });
    
    requestAnimationFrame(render3D);
}

render3D();

6. 小结

本章全面介绍了Canvas的变换与坐标系统:

  1. 坐标系统基础:Canvas坐标系统、坐标转换工具
  2. 基础变换操作:平移、旋转、缩放、组合变换
  3. 变换矩阵:矩阵运算、自定义变换效果
  4. 坐标系统管理:状态栈管理、视口和相机系统
  5. 高级变换技巧:动画变换、3D效果模拟

下一章我们将学习动画基础与帧循环,包括动画原理、帧率控制和动画优化技术。

7. 练习题

  1. 创建一个支持缩放、平移、旋转的图像查看器
  2. 实现一个2D游戏相机系统,支持跟随目标和边界限制
  3. 制作一个3D立方体旋转动画
  4. 创建一个变换动画编辑器,支持关键帧动画
  5. 实现一个支持多层变换的图形编辑器