I was thinking about implementing a simple portal renderer for my competition entry, but I ran into some problems.

The idea was to have sectors which are actually collections of objects that are rendered, these are linked to portals that tell what other sectors might be visible for the current one. I'd let OpenGL to do the actual culling of the vertices, but still try to somewhat limit the number of tris to draw.

For the portal renderer to work properly first I need to check the facing of the portal polygons to see if they even can be visible, then I check if they are inside the view frustum and do some clipping if needed.

But I can't get even the first part working properly, for some reason the portal polygons are visible when they shouldn't and vice versa.

Here's how I calculate the planes for the portals:
[pascal]
function CalcFaceNormal(V1, V2, V3: TVector3f): TVector4f;
var
rx1, ry1, rz1, rx2, ry2, rz2: TFloat;
A, B, C, D: TFloat;
len: TFloat;
begin
rx1 := V2.x - V1.x;
ry1 := V2.y - V1.y;
rz1 := V2.z - V1.z;
rx2 := V3.x - V1.x;
ry2 := V3.y - V1.y;
rz2 := V3.z - V1.z;
A := ry1 * rz2 - ry2 * rz1;
B := rz1 * rx2 - rz2 * rx1;
C := rx1 * ry2 - rx2 * ry1;
len := sqrt(A * A + B * B + C * C);
A := A / len; B := B / len; C:= C / len;
D := A * V2.x + B * V2.y + C * V2.z;
Result := _Vector4f(A, B, C, D);
end;
[/pascal]

Here's how I check if the plane is visible, the fViewVector is taken from the modelview matrix and FaceNormal is calculated with the function above:
[pascal]
function TFrustum.FaceVisible(FaceNormal: TVector4f): Boolean;
begin
Result := FaceNormal.x * fViewVector.x + FaceNormal.y * fViewVector.y + FaceNormal.z * fViewVector.z - FaceNormal.w >= 0;
end;
[/pascal]

What I'm doing wrong?