Когда-то я очень хотел создать трехмерную графику на Бейсике, и вот спустя много лет, наконец-то, взялся за дело и сделал это. Но и не только. Помимо самой графики, также научился использовать буфер для того, чтобы рисовать сначала в нем, а потом показывать на экране. Это нерационально, но все-таки, это работает.
SCREEN 13
CONST NEAR = 0.5
TYPE vec2i
x AS INTEGER
y AS INTEGER
END TYPE
TYPE vec3
x AS SINGLE
y AS SINGLE
z AS SINGLE
u AS SINGLE
v AS SINGLE
END TYPE
TYPE trg
A1 AS SINGLE: A2 AS SINGLE: A3 AS SINGLE
B1 AS SINGLE: B2 AS SINGLE: B3 AS SINGLE
C1 AS SINGLE: C2 AS SINGLE: C3 AS SINGLE
D1 AS SINGLE: D2 AS SINGLE: D3 AS SINGLE
END TYPE
DIM SHARED vc(0 TO 7) AS vec3
DIM SHARED tile(0 TO 15, 0 TO 15) AS INTEGER
DIM SHARED ma(0 TO 3, 0 TO 3) AS SINGLE
DIM SHARED mb(0 TO 3, 0 TO 3) AS SINGLE
DIM SHARED cam AS vec3
DIM SHARED rot AS vec3
DIM wr(0 TO 3, 0 TO 3, 0 TO 3) AS INTEGER
DATA -1, 1, 1
DATA 1, 1, 1
DATA 1,-1, 1
DATA -1,-1, 1
DATA -1, 1,-1
DATA 1, 1,-1
DATA 1,-1,-1
DATA -1,-1,-1
FACES:
DATA 0, 4, 7, 3
DATA 0, 1, 5, 4
DATA 4, 5, 6, 7
DATA 1, 0, 3, 2
DATA 2, 3, 7, 6
DATA 5, 1, 2, 6
FOR i = 0 TO 7: READ vc(i).x, vc(i).y, vc(i).z: NEXT
LOADPALETTE
cam.x = 1.5: cam.y = 3: cam.z = -4
rot.x = 0: rot.y = 0: rot.z = 0
wr(0,0,3)=1
wr(0,0,2)=2
wr(0,0,1)=3
wr(0,0,0)=4
wr(2,2,0)=1
wr(2,1,2)=2
CLEARBUF
FOR k = 0 TO 3
FOR i = 0 TO 3
FOR j = 0 TO 3
t = wr(j, i, k)
IF t > 0 THEN
LOADTILE t MOD 16, t \ 16
DRAWCUBE 2*j, 2*i, 2*k
END IF
NEXT
NEXT
NEXT
RESTOREBUF
SUB RENDER (p0 AS vec3, p1 AS vec3, p2 AS vec3)
DIM q(0 TO 2) AS vec3
DIM p(0 TO 3) AS vec3
q(0) = p0: q(1) = p1: q(2) = p2
DIM a AS vec3
DIM b AS vec3
DIM c AS vec3
DIM k AS INTEGER
k = 0
FOR i = 0 TO 2
qx = q(i).x * ma(0, 0) + q(i).y * ma(0, 1) + q(i).z * ma(0, 2) + ma(0, 3)
qy = q(i).x * ma(1, 0) + q(i).y * ma(1, 1) + q(i).z * ma(1, 2) + ma(1, 3)
qz = q(i).x * ma(2, 0) + q(i).y * ma(2, 1) + q(i).z * ma(2, 2) + ma(2, 3)
q(i).x = qx: q(i).y = qy: q(i).z = qz
NEXT
FOR i = 0 TO 2
a = q(i)
b = q((i + 1) MOD 3)
IF a.z >= NEAR OR b.z >= NEAR THEN
IF a.z >= NEAR THEN p(k) = a: k = k + 1
IF (a.z < b.z) THEN SWAP a, b
IF (a.z >= NEAR AND b.z < NEAR) THEN
t = (NEAR - b.z) / (a.z - b.z)
c.x = b.x + t * (a.x - b.x)
c.y = b.y + t * (a.y - b.y)
c.z = NEAR
c.u = b.u + t * (a.u - b.u)
c.v = b.v + t * (a.v - b.v)
p(k) = c: k = k + 1
END IF
END IF
NEXT
IF k = 3 OR k = 4 THEN DRAWTRI p(0), p(1), p(2)
IF k = 4 THEN DRAWTRI p(0), p(2), p(3)
END SUB
SUB DRAWTRI (a AS vec3, b AS vec3, c AS vec3)
DIM p(0 TO 2) AS vec2i
DIM h(0 TO 2) AS vec3
DIM tri AS trg
DIM cb AS STRING * 320
DIM db(0 TO 319) AS SINGLE
h(0) = a: h(1) = b: h(2) = c
FOR i = 1 TO 2
h(i).u = h(i).u - h(0).u
h(i).v = h(i).v - h(0).v
NEXT
FOR i = 0 TO 2
p(i).x = 160 + 100 * h(i).x / h(i).z
p(i).y = 100 - 100 * h(i).y / h(i).z
NEXT
ABx = p(1).x - p(0).x: ABy = p(1).y - p(0).y
ACx = p(2).x - p(0).x: ACy = p(2).y - p(0).y
IF ABy * ACx - ABx * ACy > 0 THEN EXIT SUB
FOR i = 0 TO 2
FOR j = i + 1 TO 2
IF p(i).y > p(j).y THEN SWAP p(i), p(j)
NEXT
NEXT
CALC h(0), h(1), h(2), tri
Ax = p(0).x
ACy = (p(2).y - p(0).y): IF ACy = 0 THEN ACy = 1
AC = (p(2).x - p(0).x) / ACy
ABz = h(1).z - h(0).z
ACz = h(2).z - h(0).z
FOR i = 0 TO 1
Bx = p(i).x
By = p(i + 1).y
ABy = By - p(i).y: IF ABy = 0 THEN ABy = 1
AB = (p(i + 1).x - p(i).x) / ABy
IF i = 0 THEN By = By - 1
FOR y = p(i).y TO By
IF y >= 0 AND y <= 199 THEN
th = 0
x1 = INT(Ax)
x2 = INT(Bx)
IF x1 > x2 THEN SWAP x1, x2
IF x1 < 320 AND x2 >= 0 THEN
x1 = x1 - 2
x2 = x2 + 2
IF x1 < 0 THEN x1 = 0
IF x2 > 319 THEN x2 = 319
dx = x1 - 160
dy = 100 - y
dz = 100
u = dx * tri.A1 + dy * tri.A2 + dz * tri.A3
v = dx * tri.B1 + dy * tri.B2 + dz * tri.B3
D = dx * tri.C1 + dy * tri.C2 + dz * tri.C3
FOR x = x1 TO x2
IF D <> 0 AND u >= 0 AND v >= 0 AND u + v < D THEN
ux = u / D
vx = v / D
cz = h(0).z + ux * ABz + vx * ACz
IF th = 0 THEN
th = 1
DEF SEG = VARSEG(cb): BLOAD "TMP\CX" + STR$(y) + ".BIN", VARPTR(cb): DEF SEG
DEF SEG = VARSEG(db(0)): BLOAD "TMP\DX" + STR$(y) + ".BIN", VARPTR(db(0)): DEF SEG
END IF
IF cz < db(x) THEN
tx = INT(h(0).u + ux * h(1).u + vx * h(2).u)
ty = INT(h(0).v + ux * h(1).v + vx * h(2).v)
IF tx < 0 THEN tx = 0 ELSE IF tx > 15 THEN tx = 15
IF ty < 0 THEN ty = 0 ELSE IF ty > 15 THEN ty = 15
cl% = tile(tx, ty)
IF cl% <> 0 THEN
MID$(cb, 1 + x, 1) = CHR$(cl%)
db(x) = cz
END IF
END IF
END IF
u = u + tri.A1
v = v + tri.B1
D = D + tri.C1
NEXT
END IF
IF th > 0 THEN
DEF SEG = VARSEG(cb): BSAVE "TMP\CX" + STR$(y) + ".BIN", VARPTR(cb), 320: DEF SEG
DEF SEG = VARSEG(db(0)): BSAVE "TMP\DX" + STR$(y) + ".BIN", VARPTR(db(0)), 1280: DEF SEG
END IF
END IF
Ax = Ax + AC
Bx = Bx + AB
NEXT
NEXT
END SUB
SUB DRAWCUBE(x AS SINGLE, y AS SINGLE, z AS SINGLE)
DIM p(0 TO 2) AS vec3
MATIDENT 0
CAMROT rot.z, 2
CAMROT rot.x, 0
CAMROT rot.y, 1
CAMTRAN x - cam.x, y - cam.y, z - cam.z
RESTORE FACES
FOR i = 0 TO 5
READ f0, f1, f2, f3
p(0) = vc(f0): p(0).u = 0: p(0).v = 0
p(1) = vc(f1): p(1).u = 16: p(1).v = 0
p(2) = vc(f2): p(2).u = 16: p(2).v = 16
RENDER p(0), p(1), p(2)
p(0) = vc(f0): p(0).u = 0: p(0).v = 0
p(1) = vc(f2): p(1).u = 16: p(1).v = 16
p(2) = vc(f3): p(2).u = 0: p(2).v = 16
RENDER p(0), p(1), p(2)
NEXT
END SUB
SUB CALC (p0 AS vec3, p1 AS vec3, p2 AS vec3, t AS trg)
Ax = p0.x: Ay = p0.y: Az = p0.z
Bx = p1.x: By = p1.y: Bz = p1.z
Cx = p2.x: Cy = p2.y: Cz = p2.z
ABx = Bx - Ax: ACx = Cx - Ax
ABy = By - Ay: ACy = Cy - Ay
ABz = Bz - Az: ACz = Cz - Az
t.A1 = (Ay * ACz - Az * ACy)
t.A2 = (Az * ACx - Ax * ACz)
t.A3 = (Ax * ACy - Ay * ACx)
t.B1 = (Az * ABy - Ay * ABz)
t.B2 = (Ax * ABz - Az * ABx)
t.B3 = (Ay * ABx - Ax * ABy)
t.C1 = (ABz * ACy - ABy * ACz)
t.C2 = (ABx * ACz - ABz * ACx)
t.C3 = (ABy * ACx - ABx * ACy)
END SUB
SUB CAMROT (a AS SINGLE, axis AS INTEGER)
sina = SIN(a)
cosa = COS(a)
MATIDENT 1
SELECT CASE axis
CASE 0:
mb(1, 1) = cosa: mb(1, 2) = -sina
mb(2, 1) = sina: mb(2, 2) = cosa
CASE 1:
mb(0, 0) = cosa: mb(0, 2) = sina
mb(2, 0) = -sina: mb(2, 2) = cosa
CASE 2:
mb(0, 0) = cosa: mb(0, 1) = -sina
mb(1, 0) = sina: mb(1, 1) = cosa
END SELECT
MATMULT
END SUB
SUB CAMTRAN (x AS SINGLE, y AS SINGLE, z AS SINGLE)
MATIDENT 1
mb(0, 3) = x
mb(1, 3) = y
mb(2, 3) = z
MATMULT
END SUB
SUB MATIDENT (id AS INTEGER)
FOR i = 0 TO 3
FOR j = 0 TO 3
SELECT CASE id
CASE 0: IF i = j THEN ma(i, j) = 1 ELSE ma(i, j) = 0
CASE 1: IF i = j THEN mb(i, j) = 1 ELSE mb(i, j) = 0
END SELECT
NEXT
NEXT
END SUB
SUB MATMULT
DIM mt(0 TO 3, 0 TO 3) AS SINGLE
FOR i = 0 TO 3
FOR j = 0 TO 3
s = 0
FOR k = 0 TO 3: s = s + ma(i, k) * mb(k, j): NEXT
mt(i, j) = s
NEXT
NEXT
FOR i = 0 TO 3: FOR j = 0 TO 3: ma(i, j) = mt(i, j): NEXT: NEXT
END SUB
SUB CLEARBUF
DIM cb AS STRING * 320
DIM db(0 TO 319) AS SINGLE
FOR i = 0 TO 319
cb = cb + CHR$(0)
db(i) = 32767
NEXT
FOR i = 0 TO 199
DEF SEG = VARSEG(cb): BSAVE "TMP\CX" + STR$(i) + ".BIN", VARPTR(cb), 320: DEF SEG
DEF SEG = VARSEG(db(0)): BSAVE "TMP\DX" + STR$(i) + ".BIN", VARPTR(db(0)), 1280: DEF SEG
NEXT
END SUB
SUB RESTOREBUF
DIM cb AS STRING * 320
FOR y = 0 TO 199
DEF SEG = &HA000
BLOAD "TMP\CX" + STR$(y) + ".BIN", 320 * y
DEF SEG
NEXT
END SUB
SUB LOADPALETTE
DIM pal AS STRING * 3
OPEN "CRAFT.BMP" FOR BINARY AS #1
FOR i = 1 TO 255
GET #1, &H36 + 1 + i * 4, pal
OUT 968, i
OUT 969, ASC(MID$(pal, 3, 1)) \ 4
OUT 969, ASC(MID$(pal, 2, 1)) \ 4
OUT 969, ASC(MID$(pal, 1, 1)) \ 4
NEXT i
CLOSE #1
END SUB
SUB LOADTILE (j AS INTEGER, i AS INTEGER)
DIM dat AS STRING * 16
OPEN "CRAFT.BMP" FOR BINARY AS #1
FOR y = 0 TO 15
GET #1, &H437 + (255 - 16 * i - y) * 256 + 16 * j, dat
FOR x = 0 TO 15: tile(x, y) = ASC(MID$(dat, 1 + x, 1)): NEXT
NEXT
CLOSE #1
END SUB