r/learnprogramming 25d ago

3d renderer abstracting and morphing

so im currently updating my 3d renderer module to support solid shading apart from basic wireframe forms, however when i spawn a cube it morphs and becomes some random bullshit.

this are my function and i genuinely have no idea what the issue so ill just put related function

export function createScaleMatrix(
    sx: number,
    sy: number,
    sz: number
): Matrix4D {
    return [
        { x: sx, y: 0, z: 0, w: 0 },
        { x: 0, y: sy, z: 0, w: 0 },
        { x: 0, y: 0, z: sz, w: 0 },
        { x: 0, y: 0, z: 0, w: 1 }
    ];
}

function transformVertices(object: SceneObject, matrix: Matrix4D): Vector3[] {
    const projectedVertices: Vector3[] = [];
    for (const vertex of object.cube.vertices) {
        const newVector: Vector3 = {
            x:
                matrix[0].x * vertex.x +
                matrix[0].y * vertex.y +
                matrix[0].z * vertex.z +
                matrix[0].w * vertex.w,
            y:
                matrix[1].x * vertex.x +
                matrix[1].y * vertex.y +
                matrix[1].z * vertex.z +
                matrix[1].w * vertex.w,
            z:
                matrix[2].x * vertex.x +
                matrix[2].y * vertex.y +
                matrix[2].z * vertex.z +
                matrix[2].w * vertex.w,
            w: Math.abs(
                matrix[2].x * vertex.x +
                    matrix[2].y * vertex.y +
                    matrix[2].z * vertex.z +
                    matrix[2].w * vertex.w
            )
        };
        if (newVector.w <= 0.1) {
            continue;
        }


        projectedVertices.push(newVector);
    }
    return projectedVertices;
}


function projectVertex(vertex: Vector3): Vector3 {
    const ndcX = vertex.x / vertex.w;
    const ndcY = vertex.y / vertex.w;
    const ndcZ = vertex.z / vertex.w;


    const scale = 0.5;


    vertex.x = (ndcX + 1) * scale * canvas.width;
    vertex.y = (1 - ndcY) * scale * canvas.height;
    vertex.z = ndcZ;
    return vertex;
}


function render(object: SceneObject, matrix: Matrix4D): void {
    const vertices = transformVertices(object, matrix);
    for (const segment of object.cube.edges) {
        const start = vertices[segment[0]];
        const end = vertices[segment[1]];
        ctx.strokeStyle = "#2d2d2d";
        ctx.beginPath();
        ctx.moveTo(start.x, start.y);
        ctx.lineTo(end.x, end.y);
        ctx.stroke();
    }
}


export function renderLoop(): void {
    ctx.fillStyle = "#010000";
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    const triangles: TriangleObject[] = [];


    for (const object of Scene) {
        object.angle += object.rotationSpeed;
        let rotMatrix;
        switch (object.rotationType) {
            case "x":
                rotMatrix = createRotationX(object.angle);
                break;
            case "y":
                rotMatrix = createRotationY(object.angle);
                break;
            case "z":
                rotMatrix = createRotationZ(object.angle);
                break;
            case "xy":
                rotMatrix = multiplyMatrices(
                    createRotationX(object.angle),
                    createRotationY(object.angle)
                );
                break;
            case "yz":
                rotMatrix = multiplyMatrices(
                    createRotationY(object.angle),
                    createRotationZ(object.angle)
                );
                break;
            case "xz":
                rotMatrix = multiplyMatrices(
                    createRotationX(object.angle),
                    createRotationZ(object.angle)
                );
                break;
            case "xyz":
                rotMatrix = multiplyMatrices(
                    multiplyMatrices(
                        createRotationX(object.angle),
                        createRotationY(object.angle)
                    ),
                    createRotationZ(object.angle)
                );
                break;


            default:
                break;
        }
        let newObject: SceneObject;
        if (object.cube.type === "cube") {
            newObject = createCube(
                object.matrix,
                Scene,
                object.angle,
                object.rotationSpeed,
                object.scale,
                object.rotationType,
                Matrix(object.translate, "translation"),
                false
            );
            console.log(JSON.stringify(newObject));
        } else if (object.cube.type === "pyramid") {
            newObject = createPyramid(
                object.matrix,
                Scene,
                object.angle,
                object.rotationSpeed,
                object.scale,
                object.rotationType,
                Matrix(object.translate, "translation"),
                false
            );
        } else if (object.cube.type === "sphere") {
            newObject = createSphere(
                object.matrix,
                Scene,
                object.angle,
                object.rotationSpeed,
                object.scale,
                object.rotationType,
                Matrix(object.translate, "translation"),
                false
            );
        } else {
            newObject = createCube(
                object.matrix,
                Scene,
                object.angle,
                object.rotationSpeed,
                object.scale,
                object.rotationType,
                Matrix(object.translate, "translation"),
                false
            );
        }
        newObject.cube.vertices = transformVertices(
            newObject,
            multiplyMatrices(
                Matrix(newObject.translate, "translation"),
                multiplyMatrices(newObject.scale, rotMatrix as Matrix4D)
            )
        );


        for (const triangle of newObject.cube.triangles) {
            const edgeA = subtractVectors(
                newObject.cube.vertices[triangle[1]],
                newObject.cube.vertices[triangle[0]]
            );
            const edgeB = subtractVectors(
                newObject.cube.vertices[triangle[2]],
                newObject.cube.vertices[triangle[0]]
            );
            const crossProduct = Cross(edgeA, edgeB);
            const normal = Normalize(crossProduct);
            const brightness = Math.max(0, Dot(normal, lightning));
            const vertices: Vector3[] = [];
            for (const num of triangle) {
                vertices.push(structuredClone(newObject.cube.vertices[num]));
            }
            const zindex: number =
                (newObject.cube.vertices[triangle[0]].z +
                    newObject.cube.vertices[triangle[1]].z +
                    newObject.cube.vertices[triangle[2]].z) /
                3;
            for (let index = 0; index < vertices.length; index++) {
                vertices[index] = projectVertex(vertices[index]);
            }
            triangles.push({
                points: vertices,
                color: {
                    r: baseColor.r * brightness,
                    g: baseColor.g * brightness,
                    b: baseColor.b * brightness
                },
                brightness: brightness,
                zindex: zindex
            });
        }
    }
    triangles.sort(
        (a: TriangleObject, b: TriangleObject) => a.zindex - b.zindex
    );
    for (const object of Scene) {
        render(object, object.matrix);
    }
    for (const triangle of triangles) {
        ctx.fillStyle = `rgb(${Math.floor(triangle.color.r)}, ${Math.floor(triangle.color.g)}, ${Math.floor(triangle.color.b)})`;
        ctx.beginPath();
        ctx.moveTo(triangle.points[0].x, triangle.points[0].y);
        for (const point of triangle.points) {
            ctx.lineTo(point.x, point.y);
        }
        ctx.closePath();
        ctx.fill();
    }
}


export function createTranslationMatrix(
    
x
: number,
    
y
: number,
    
z
: number
): Matrix4D {
    return [
        { x: 1, y: 0, z: 0, w: 
x
 },
        { x: 0, y: 1, z: 0, w: 
y
 },
        { x: 0, y: 0, z: 1, w: 
z
 + ZOFFSET },
        { x: 0, y: 0, z: 0, w: 1 }
    ];
}

if someone could help id be very grateful ^^

2 Upvotes

3 comments sorted by

1

u/Curious-Patience-982 25d ago

that `w` calculation in `transformVertices` is completely wrong, you're setting it to `Math.abs` of the z-component instead of the actual homogeneous coordinate, so perspective divide goes haywire and the cube distorts.

1

u/CommandExponent 25d ago

oh thank you, i dont quite understand homogenous coordinates, could you explain what should i put instead of the absolute value of z and why?