OpenB3D Questions

Started by Krischan, January 04, 2019, 23:33:07

Previous topic - Next topic

Krischan

Hi there. I've stopped working with Blitzmax and OpenB3D a few months ago due to a great showstopper problem with OpenB3D. Now I started to work on it again and came across new topics. I modified one of my techdemos so that you can play around with it. Maybe Mark or someone else can help here.

Background: OpenB3D should be mostly syntax compatible to MiniB3D. So I wrote the same demo once in the latest MiniB3D version and once parallel in the newest OpenB3D release from Github. Actually these source codes should be almost the same in syntax, but I noticed different results in some parts in OpenB3D and had to code some workarounds. I'm still using BlitzmaxOS, BLIDE and a BMK.exe from October 2017 (because of BLIDE). MinGW is gcc.exe (tdm64-1) 5.1.0 here.

The demo is visually quite simple, but has - as always - a complex sourcecode. I tried to stay below 1000 lines - I've attached both sources and the compiled executables here to this post. The demo shows an endless star field, which consists of 6.250.000 single stars (yes, correctly read). The star texture is generated procedurally by the code so no external media is needed. You can move around with WASD, QE and RF - hold TAB for Turbo. I've added three Debug stages which you can enable by changing the DEBUGLEVEL variable at the top of the source, together with other variables there. Level 1 disables Instancing and calculates a limited starfield using the TQuad rotations only for ALL stars, which is very slow. Level 2 disables textures and shows the 125 clusters colored, visualizing the teleport effect. And Level 3 makes it possible to leave the Cube to see the whole structure.

To make the demo still run with 60FPS I use Instancing with CopyEntity. First, a single star cluster with 50,000 stars is calculated, which is then duplicated in a 5x5x5 matrix in a cube. The camera only moves around in the innermost cube and is teleported to the opposite side of the inner cluster when reaching the edge. Since all clusters are visually identical, this teleporting is nearly not noticeable and the impression of an infinite star field is created.

Now to the problems / questions regarding OpenB3D: ???

1) Line 99: I must add "SetBlend ALPHABLEND" here or the initialization in OpenB3D looks very strange - MiniB3D doesn't have this problem. Generally the blending seems to work differently than in MiniB3D. Comment this line and see yourself :-)

2) Line 111 / 257 / TQuad Type: why are the vertex coordinate changes in CopyEntity not copied to all children meshes live like in MiniB3D? I manipulate the quads only in the source mesh, but the copies are ignored? Is it a bug? Or still not implemented? This is IMHO a very important feature of 3D graphics.

3) Line 102-185: why does the initialization take much longer with OpenB3D than with MiniB3D? MiniB3D: 1250ms vs. OpenB3D: 3000ms

4) Line 739: why do I must calculate the Cameraviewport Y value from the bottom left? In MiniB3D I use "CameraViewport tempcam, 0, 0, size, size" (top left) and in OpenB3D I must use "GraphicsHeight() - size" for the Y-coordinate to get the same result. I know that OpenGL calculates this way, but it is confusing compared to Blitz3D/MiniB3D

5) Line 205-206: in OpenB3D the mouse pointer moves slightly after initialization, this can only be fixed by NOT subtracting -1 in GraphicsHeight() - in MiniB3D it works (and would also be mathematically correct) - is it a rounding bug?

6) Line 279-285: I must use my own Begin2D/End2D functions here instead of the BeginMax2D/EndMax2D, or all vertex colors vanish and the universe is grey (replace my Commands with the default ones and see yourself). But why is this? It could be connected to the first problem noticed here.

7) Line 846: in MiniB3D I could access a Texture's pixmap directly by using the "glBindTexture (GL_TEXTURE_2D, tex.gltex[0])" command. How do I access the Pixmap of a Texture in OpenB3D? Is it only possible with the "BufferToTex tex, PixmapPixelPtr(pixmap, 0, 0)" command I've used instead?

8. @Mark: has OpenB3D development stopped four months ago or did you just not have time for it anymore? Am I the only one still using it? :o

Oh, and even if you can't help - have fun with the demo ;D It was a hard piece of work to get BOTH versions running.

MiniB3D Version:
Code (blitzmax) Select
SuperStrict

Import sidesign.minib3d

Graphics3D DesktopWidth() / 1.5, DesktopHeight() / 1.5, 32, 2

ClearTextureFilters()

Global STARS:Int = 50000        ' number of stars per cluster part
Global DIMENSION:Int = 2000     ' cluster dimension
Global DISTRIBUTION:Float = 0.99' star distribution (0...1 = random...clustered)
Global SPEED:Float = 1.0        ' overall move speed multiplicator
Global BRAKE:Float = 0.95       ' brake multiplicator
Global VSYNC:Int = False        ' Vertical Sync
Global HOMING:Int = True        ' turn homing on/off
Global COLORED:Int = False      ' colorize clusters
Global TEXTURED:Int = True      ' use star texture
Global Scale:Float = 0.5        ' star scale factor
Global FADE:Int = True          ' fade distant clusters
Global INSTANCING:Int = True    ' duplicate source mesh

Global DEBUGLEVEL:Int = 0       ' 0=off, 1=no instancing, 2=colored, 3=scene

If DEBUGLEVEL > 0 Then

STARS = 1000
Scale = 1.6
DISTRIBUTION = 0.5
INSTANCING = False

If DEBUGLEVEL > 1 Then

COLORED = True
TEXTURED = False

If DEBUGLEVEL > 2 Then

HOMING = False
FADE = False

EndIf

EndIf

EndIf

' ----------------------------------------------------------------------------
' Global variables
' ----------------------------------------------------------------------------
Global localx:Float, localy:Float, localz:Float
Global globalx:Float, globaly:Float, globalz:Float
Global simx:Float, simy:Float, simz:Float
Global xspeed:Float, yspeed:Float, zspeed:Float, rspeed:Float
Global xadd:Float, yadd:Float, zadd:Float, radd:Float, tadd:Float

Global FPS:TFPS = New TFPS
Global MS:Int = MilliSecs()

' ----------------------------------------------------------------------------
' Init scene
' ----------------------------------------------------------------------------
Global masterpivot:TPivot = CreatePivot()

Global cam:TCamera = CreateCamera()
CameraRange cam, 1, DIMENSION * 8
PositionEntity cam, 0, 0, 0

Local tex:TTexture = CreateSunTexture(512, 128, 128, 128)
TextureBlend tex, 3

' ----------------------------------------------------------------------------
' Duplicate Quad mesh in a 9x9x9 matrix
' ----------------------------------------------------------------------------
Global meshlist:TList = CreateList()
Global quadlist:TList = CreateList()
Global quadsourcelist:TList = CreateList()
Global lx:Float, ly:Float, lz:Float

Local entities:TEntity[9, 9, 9]
Local meshes:TMesh[9, 9, 9]

Local mesh:TMesh

' star cluster properties
mesh = CreateMesh()
EntityFX mesh, 1 + 2
EntityBlend mesh, 3
If TEXTURED Then EntityTexture mesh, tex
HideEntity mesh

' create blueprint star cluster
For Local i:Int = 1 To STARS

CreateStar(mesh)

Next

' fix for OpenB3D to enable Texture Blending and Alpha?
SetBlend ALPHABLEND

' create 5x5x5 = 125 cluster
For Local x:Int = 0 To 4 Step 1

For Local y:Int = 0 To 4 Step 1

For Local z:Int = 0 To 4 Step 1

' create a mesh and duplicate its properties = FAST
If INSTANCING Then

entities[x, y, z] = CopyEntity(mesh)

PositionEntity entities[x, y, z], (x - 2) * DIMENSION, (y - 2) * DIMENSION, (z - 2) * DIMENSION

If COLORED Then

EntityColor entities[x, y, z], Rand(64, 255), Rand(64, 255), Rand(64, 255)

Else

EntityColor entities[x, y, z], 255, 255, 255

EndIf

ListAddLast(meshlist, entities[x, y, z])

' create many unique meshes without instancing = SLOW
Else

' new mesh
meshes[x, y, z] = CreateMesh()
EntityFX meshes[x, y, z], 1
EntityBlend meshes[x, y, z], 3
If TEXTURED Then

EntityTexture meshes[x, y, z], tex
EntityFX meshes[x, y, z], 1 + 2

EndIf

' duplicate blueprint cluster
For Local s:TQuad = EachIn quadsourcelist

Local q:TQuad = New TQuad

q.mesh = meshes[x, y, z]
q.x = s.x
q.y = s.y
q.z = s.z
q.scalex = s.scalex
q.scaley = s.scaley
q.RGB = s.RGB

q.Add()
q.Update(cam)

ListAddLast(quadlist, q)

Next

PositionEntity meshes[x, y, z], (x - 2) * DIMENSION, (y - 2) * DIMENSION, (z - 2) * DIMENSION

If COLORED Then

EntityColor meshes[x, y, z], Rand(128, 255), Rand(128, 255), Rand(128, 255)

Else

EntityColor meshes[x, y, z], 255, 255, 255

EndIf

ListAddLast(meshlist, meshes[x, y, z])

EndIf

RenderWorld

Flip False

Next

Next

Next

' Fix to really center the mouse (otherwise it "jumps" a little bit initially)
MoveMouse(GraphicsWidth() / 2, GraphicsHeight() / 2) ; Flip
MouseX()
MouseY()
MouseXSpeed()
MouseYSpeed()
Flip

MS = MilliSecs() - MS

' ----------------------------------------------------------------------------
' Main Loop
' ----------------------------------------------------------------------------
While Not AppTerminate()

If KeyHit(KEY_ESCAPE) Then End

' axis rotation
Local pitch:Float = Normalize(MouseY(), 0, GraphicsHeight() - 1, -2, 2)
Local yaw:Float = Normalize(MouseX(), 0, GraphicsWidth() - 1, 2, -2)
Local roll:Int = KeyDown(KEY_Q) - KeyDown(KEY_E)

' XYZ movement
Local xmove:Int = KeyDown(KEY_D) - KeyDown(KEY_A)
Local ymove:Int = KeyDown(KEY_R) - KeyDown(KEY_F)
Local zmove:Int = KeyDown(KEY_W) - KeyDown(KEY_S)
Local turbo:Int = KeyDown(KEY_TAB)

If xmove = 0 And xadd <> 0 Then xadd = 0 Else If xmove And xadd = 0 Then xadd = 0.001
If ymove = 0 And yadd <> 0 Then yadd = 0 Else If ymove And yadd = 0 Then yadd = 0.001
If zmove = 0 And zadd <> 0 Then zadd = 0 Else If zmove And zadd = 0 Then zadd = 0.001
If roll = 0 And radd <> 0 Then radd = 0 Else If roll And radd = 0 Then radd = 0.01
If turbo = 0 And tadd <> 0 Then tadd:*0.9 Else If turbo And tadd = 0 Then tadd = 0.1

If tadd < 0.01 Then tadd = 0.0

If Abs(xmove) Then xadd:+0.01
If Abs(ymove) Then yadd:+0.01
If Abs(zmove) Then zadd:+0.01
If Abs(roll) Then radd:+0.025
If Abs(turbo) Then tadd:+0.05

xadd:*1.025
yadd:*1.025
zadd:*1.025
radd:*1.025
tadd:*1.025

If xadd > 2.0 Then xadd = 2.0
If yadd > 2.0 Then yadd = 2.0
If zadd > 2.0 Then zadd = 2.0
If radd > 1.5 Then radd = 1.5
If tadd > 25.0 Then tadd = 25.0

' calculate movespeed/rollspeed
If Abs(xmove) Then xspeed = xadd * SPEED * Sgn(xmove) * (1.0 + tadd)
If Abs(ymove) Then yspeed = yadd * SPEED * Sgn(ymove) * (1.0 + tadd)
If Abs(zmove) Then zspeed = zadd * SPEED * Sgn(zmove) * (1.0 + tadd)
If Abs(roll) Then rspeed = radd * Sgn(roll)

' perform rotation and motion
Turn(cam, pitch, yaw, rspeed)
MoveEntity cam, xspeed, yspeed, zspeed

' keep camera at home
If HOMING Then Home(cam, masterpivot, DIMENSION)

' only the master instance quads are facing to the camera
For Local q:TQuad = EachIn quadlist

q.Update(cam)

Next

' fade mesh according to its distance
If FADE Then

For Local e:TEntity = EachIn meshlist

Local d:Float = EntityDistance(cam, e)
Local a:Float = Normalize(d, DIMENSION * 1.0, DIMENSION * 2.0, 1.0, 0.0)
EntityAlpha e, a

Next

EndIf

RenderWorld

FPS.Update()

' fix for OpenB3D to have vertex colors enabled (otherwise its greyscale)
Begin2D()

DrawText("FPS [Init]..: " + FPS.FPS + " [" + MS + "ms]", 0, 0)
DrawText("Star Cluster: " + STARS, 0, 15)
DrawText("Stars Total.: " + (STARS * 5 * 5 * 5), 0, 30)

End2D()

Flip VSYNC

' decrease speed
xspeed:*BRAKE
yspeed:*BRAKE
zspeed:*BRAKE
rspeed:*BRAKE

Wend

End

' creates a single star quad
Function CreateStar(mesh:TMesh, surf:TSurface = Null, xoff:Float = 0.0:Float, yoff:Float = 0.0, zoff:Float = 0.0)

Local q:TQuad = New TQuad

q.mesh = mesh
q.surf = surf

q.x = Rnd(-DIMENSION / 2, DIMENSION / 2) + xoff
q.y = Rnd(-DIMENSION / 2, DIMENSION / 2) + yoff
q.z = Rnd(-DIMENSION / 2, DIMENSION / 2) + zoff

' randomized clustering
If Rnd(1) > 1 - DISTRIBUTION Then

Local d:Float = Rnd(0.0001, 0.05)

q.x = lx + Rnd(-DIMENSION * d, DIMENSION * d) + xoff
q.y = ly + Rnd(-DIMENSION * d, DIMENSION * d) + yoff
q.z = lz + Rnd(-DIMENSION * d, DIMENSION * d) + zoff

EndIf

q.scalex = Rnd(1, Rnd(2, Rnd(4, Rnd(8, Rnd(16, 32))))) * Scale
q.scaley = q.scalex

Local c:Float = (1.0 / q.scalex)
If Rnd(1) > 0.9 And c < 0.70 Then c = 1 - c             ' 10% Giants
If c >= 0.0 And c < 0.35 Then q.RGB = [255, 64, 0]      ' 35% Class M
If c >= 0.35 And c < 0.50 Then q.RGB = [255, 128, 32]   ' 15% Class K
If c >= 0.50 And c < 0.60 Then q.RGB = [255, 255, 64]   ' 10% Class G
If c >= 0.60 And c < 0.70 Then q.RGB = [255, 255, 128]  ' 10% Class F
If c >= 0.70 And c < 0.80 Then q.RGB = [255, 255, 255]  ' 10% Class A
If c >= 0.80 And c < 0.90 Then q.RGB = [0, 64, 255]     ' 10% Class B
If c >= 0.90 And c <= 1.00 Then q.RGB = [0, 0, 255]     ' 10% Class O

q.Add()
q.Update(cam)

ListAddLast(quadlist, q)
ListAddLast(quadsourcelist, q)

lx = q.x
ly = q.y
lz = q.z

End Function

' keeps the player in a predefined cubic area
Function Home(target:TEntity, world:TEntity, homesize:Int = 100)

' store Local player Position
Local localx:Double = EntityX(target)
Local localy:Double = EntityY(target)
Local localz:Double = EntityZ(target)

' Check X axis
While localx > homesize

globalx:+homesize
localx:-homesize
PositionEntity target, localx, localy, localz
MoveEntity world, -homesize, 0, 0

Wend

While localx < - homesize

globalx:-homesize
localx:+homesize
PositionEntity target, localx, localy, localz
MoveEntity world, homesize, 0, 0

Wend

' Check Y axis
While localy > homesize

globaly:+homesize
localy:-homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, -homesize, 0

Wend

While localy < - homesize

globaly:-homesize
localy:+homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, homesize, 0

Wend

' Check Z axis
While localz > homesize

globalz:+homesize
localz:-homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, 0, -homesize

Wend

While localz < - homesize

globalz:-homesize
localz:+homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, 0, homesize

Wend

' store simulated player Position
simx = localx + globalx
simy = localy + globaly
simz = localz + globalz

End Function

' ----------------------------------------------------------------------------
' Normalizes a value to given range
' ----------------------------------------------------------------------------
Function Normalize:Float(value:Float, vmin:Float, vmax:Float, nmin:Float, nmax:Float)

Return((value - vmin) / (vmax - vmin)) * (nmax - nmin) + nmin

End Function

' ----------------------------------------------------------------------------
' Turn Entity using Quaternions
' ----------------------------------------------------------------------------
Function Turn(Ent:TEntity, X:Float = 0.0, Y:Float = 0.0, Z:Float = 0.0, Glob:Int = False)

Local Pitch:Float = 0.0
Local Yaw:Float = 0.0
Local Roll:Float = 0.0

Local Quat:TQuaternion = EulerToQuat(0.0, 0.0, 0.0)
Local Turn_Quat:TQuaternion = EulerToQuat(0.0, 0.0, 0.0)

If Glob = False

Quat = EulerToQuat(EntityPitch(ent, True), EntityYaw(ent, True), EntityRoll(ent, True))
Turn_Quat = EulerToQuat(X, Y, Z)
Quat = MultiplyQuats(Quat, Turn_Quat)
Quat = NormalizeQuat(Quat)
QuatToEuler2(Quat.X, Quat.Y, Quat.Z, Quat.w, Pitch, Yaw, Roll)
RotateEntity Ent, Pitch, Yaw, Roll

Else

RotateEntity Ent, EntityPitch(Ent) + X, EntityYaw(Ent) + Y, EntityRoll(Ent) + Z

EndIf

End Function

' ----------------------------------------------------------------------------
' Euler to Quaternion
' ----------------------------------------------------------------------------
Function EulerToQuat:TQuaternion(pitch:Float, yaw:Float, roll:Float)

Local cr:Float = Cos(-roll / 2.0)
Local cp:Float = Cos(pitch / 2.0)
Local cy:Float = Cos(yaw / 2.0)
Local sr:Float = Sin(-roll / 2.0)
Local sp:Float = Sin(pitch / 2.0)
Local sy:Float = Sin(yaw / 2.0)
Local cpcy:Float = cp * cy
Local spsy:Float = sp * sy
Local spcy:Float = sp * cy
Local cpsy:Float = cp * sy

Local q:TQuaternion = New TQuaternion

q.w = cr * cpcy + sr * spsy
q.x = sr * cpcy - cr * spsy
q.y = cr * spcy + sr * cpsy
q.z = cr * cpsy - sr * spcy

Return q

End Function

' ----------------------------------------------------------------------------
' Quaternion to Euler
' ----------------------------------------------------------------------------
Function QuatToEuler2(x:Float, y:Float, z:Float, w:Float, pitch:Float Var, yaw:Float Var, roll:Float Var)

Local QuatToEulerAccuracy:Double = 1.0 / 2 ^ 31

Local sint:Float = (2.0 * w * y) - (2.0 * x * z)
Local cost_temp:Float = 1.0 - (sint * sint)
Local cost:Float

If Abs(cost_temp) > QuatToEulerAccuracy

cost = Sqr(cost_temp)

Else

cost = 0.0

EndIf

Local sinv:Float, cosv:Float, sinf:Float, cosf:Float

If Abs(cost) > QuatToEulerAccuracy

sinv = ((2.0 * y * z) + (2.0 * w * x)) / cost
cosv = (1.0 - (2.0 * x * x) - (2.0 * y * y)) / cost
sinf = ((2.0 * x * y) + (2.0 * w * z)) / cost
cosf = (1.0 - (2.0 * y * y) - (2.0 * z * z)) / cost

Else

sinv = (2.0 * w * x) - (2.0 * y * z)
cosv = 1.0 - (2.0 * x * x) - (2.0 * z * z)
sinf = 0.0
cosf = 1.0

EndIf

pitch = ATan2(sint, cost)
yaw = ATan2(sinf, cosf)
roll = -ATan2(sinv, cosv)

End Function

' ----------------------------------------------------------------------------
' Multiply Quaternion
' ----------------------------------------------------------------------------
Function MultiplyQuats:TQuaternion(q1:TQuaternion, q2:TQuaternion)

Local q:TQuaternion = New TQuaternion

q.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z
q.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y
q.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z
q.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x

Return q

End Function

' ----------------------------------------------------------------------------
' Normalize Quaternion
' ----------------------------------------------------------------------------
Function NormalizeQuat:TQuaternion(q:TQuaternion)

Local uv:Float = Sqr(q.w * q.w + q.x * q.x + q.y * q.y + q.z * q.z)

q.w = q.w / uv
q.x = q.x / uv
q.y = q.y / uv
q.z = q.z / uv

Return q

End Function

' ------------------------------------------------------------------------------------------------
' Quad Type
' ------------------------------------------------------------------------------------------------
Type TQuad

Field x:Float = 0.0 ' position x
Field y:Float = 0.0 ' position y
Field z:Float = 0.0 ' position z

Field row:Int = 0 ' texture row
Field col:Int = 0 ' texture column

Field scalex:Float = 1.0 ' current size X
Field scaley:Float = 1.0 ' current size Y

Field RGB:Int[] = [255, 255, 255] ' Vertex Color
Field Alpha:Float = 1.0 ' Vertex Alpha

Field v:Int = 0 ' vertex counter
Field mesh:TMesh = Null ' mesh pointer
Field surf:TSurface = Null ' surface pointer

' --------------------------------------------------------------------------------------------
' METHOD: Add new Quad
' --------------------------------------------------------------------------------------------
Method Add(col:Int = 0, row:Int = 0)

Local s:Int = CountSurfaces(mesh)
If s = 0 Then

surf = CreateSurface(mesh)

Else

surf = GetSurface(mesh, s)

EndIf

Local i:Int = CountVertices(surf)
If i + 4 > 32768 Then

surf = CreateSurface(mesh)
v = 0

EndIf

' add Vertices
Local V0:Int = AddVertex(surf, 0, 0, 0, 1, 0)
Local V1:Int = AddVertex(surf, 0, 0, 0, 1, 1)
Local V2:Int = AddVertex(surf, 0, 0, 0, 0, 1)
Local V3:Int = AddVertex(surf, 0, 0, 0, 0, 0)

' color vertices
VertexColor surf, V0, RGB[0], RGB[1], RGB[2], Alpha
VertexColor surf, V1, RGB[0], RGB[1], RGB[2], Alpha
VertexColor surf, V2, RGB[0], RGB[1], RGB[2], Alpha
VertexColor surf, V3, RGB[0], RGB[1], RGB[2], Alpha

' connect triangles
AddTriangle surf, V0, V1, V2
AddTriangle surf, V0, V2, V3

VertexTexCoords surf, V0, col + 1, row
VertexTexCoords surf, V1, col + 1, row + 1
VertexTexCoords surf, V2, col, row + 1
VertexTexCoords surf, V3, col, row

' increase vertex counter
If v >= 4 Then v = V0 + 4 Else v = V0

End Method

' --------------------------------------------------------------------------------------------
' METHOD: Update a Quad
' --------------------------------------------------------------------------------------------
Method Update(target:TEntity)

TFormVector scalex, 0, 0, target, Null
Local X1:Float = TFormedX()
Local Y1:Float = TFormedY()
Local Z1:Float = TFormedZ()

TFormVector 0, scaley, 0, target, Null
Local X2:Float = TFormedX()
Local Y2:Float = TFormedY()
Local Z2:Float = TFormedZ()

' set vertices
VertexCoords surf, v + 0, x - x1 - x2, y - y1 - y2, z - z1 - z2
VertexCoords surf, v + 1, x - x1 + x2, y - y1 + y2, z - z1 + z2
VertexCoords surf, v + 2, x + x1 + x2, y + y1 + y2, z + z1 + z2
VertexCoords surf, v + 3, x + x1 - x2, y + y1 - y2, z + z1 - z2

End Method

End Type

' ------------------------------------------------------------------------------------------------
' Exact FPS counter
' ------------------------------------------------------------------------------------------------

Type TFPS

Global renders:Int = 0

Field FPS:Int = 60 ' current FPS value
Field interval:Int = 999
Field averagefps:Int = 60 ' average FPS value
Field framedrops:Int = 0 ' dropped frames
Field oldlooptime:Int = 0
Field looptime:Int = 0
Field multi:Float ' main loop multiplicator

Field old:Int = MilliSecs()
Field totalfps:Int = 30
Field Ticks:Int

' --------------------------------------------------------------------------------------------
' METHOD: Update FPS counter
' --------------------------------------------------------------------------------------------
Method Update()

renders:+1

looptime = MilliSecs() - oldlooptime
oldlooptime = MilliSecs()

If MilliSecs() - old >= interval Then

old = MilliSecs()

FPS = renders * (1000 / interval)
renders = 0
totalfps:+FPS
Ticks:+1

averagefps = totalfps / Ticks

EndIf

End Method

End Type

' Create a stunning star texture
Function CreateSunTexture:TTexture(size:Int = 512, r:Int = 255, g:Int = 255, b:Int = 255)

Local i:Float, j:Int, col:Int, rgb:Int
Local x:Int, y:Int

Local w:Int = GraphicsWidth()

Local tempcam:TCamera, tempsun:TPivot
Local pixmap:TPixmap = CreatePixmap(size, size, PF_RGBA8888)
Local tex:TTexture = CreateTexture(size, size, 1 + 8)

For j = 0 To (size / 2) - 1

col = 255 - Normalize(j, 0, (size / 2.0) - 1, 0, 255)
If col > 255 Then col = 255
rgb = col * $1000000 + col * $10000 + col * $100 + col

For i = 0 To 360 Step 0.1

WritePixel(pixmap, (size / 2) + (Sin(i) * j), (size / 2) + (Cos(i) * j), rgb)

Next

Next

PixmapToTexture(pixmap, tex)
'SavePixmapPNG(pixmap, "proceduralstar1.png", 0)

' temp camera
tempcam = CreateCamera()
CameraRange tempcam, 1, w * 2

' no fix needed compared to OpenB3D
CameraViewport tempcam, 0, 0, size, size

' temp pivot
tempsun = CreatePivot()

' Create 4 body quads
CreateQuad(tempsun, size * 1.0, tex, 3, 1 + 8 + 16, r * 1.00, g * 1.00, b * 1.00, 1.00)
CreateQuad(tempsun, size * 1.5, tex, 3, 1 + 8 + 16, r * 1.00, g * 1.00, b * 1.00, 1.00)
CreateQuad(tempsun, size * 2.0, tex, 3, 1 + 8 + 16, r * 0.75, g * 0.75, b * 0.50, 0.75)
CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, r * 0.50, g * 0.50, b * 0.50, 0.50)

' create 4 ray quads
Local ray1:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 1)
Local ray2:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 1)
Local ray3:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 0.5)
Local ray4:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 0.5)

' rescale and rotate rays
ScaleEntity ray1, size * 2, size / 32, size
ScaleEntity ray2, size / 32, size * 2, size
ScaleEntity ray3, size, size / 32, size
ScaleEntity ray4, size, size / 32, size
RotateEntity ray3, 0, 0, 45
RotateEntity ray4, 0, 0, 135

PositionEntity tempsun, 0, 0, w

' render star
RenderWorld
RenderWorld

' grab image
pixmap = GrabPixmap(0, 0, size, size)
For x = 0 To size - 1

For y = 0 To size - 1

rgb = ReadPixel(pixmap, x, y)

r = (rgb & $ff0000) / $10000
g = (rgb & $ff00) / $100
b = (rgb & $ff)

' rgb
rgb = (r + g + b) / 3 * $1000000 + r * $10000 + g * $100 + b

WritePixel(pixmap, x, y, rgb)

Next

Next

PixmapToTexture(pixmap, tex)
'SavePixmapPNG(pixmap, "proceduralstar2.png", 0)

' Delete pixmap, pivot and temp cam
pixmap = Null
FreeEntity tempsun
FreeEntity tempcam

Return tex

End Function

' custom quad creation
Function CreateQuad:Tmesh(parent:TEntity, scale:Float = 1.0, tex:TTexture = Null, blend:Int = False, fx:Int = False, r:Int = 255, g:Int = 255, b:Int = 255, a:Float = 1.0)

Local mesh:TMesh = CreateMesh()
Local surf:TSurface = CreateSurface(mesh)
Local v0:Int, v1:Int, v2:Int, v3:Int

v0 = AddVertex(surf, 1, 1, 0, 0, 0)
v1 = AddVertex(surf, -1, 1, 0, 1, 0)
v2 = AddVertex(surf, -1, -1, 0, 1, 1)
v3 = AddVertex(surf, 1, -1, 0, 0, 1)

AddTriangle surf, v0, v1, v2
AddTriangle surf, v0, v2, v3

If parent Then EntityParent Mesh, parent
If fx Then EntityFX Mesh, fx
If tex Then EntityTexture Mesh, tex
If blend Then EntityBlend Mesh, blend

EntityColor Mesh, r, g, b
EntityAlpha Mesh, a

VertexColor surf, v0, r, g, b, a
VertexColor surf, v1, r, g, b, a
VertexColor surf, v2, r, g, b, a
VertexColor surf, v3, r, g, b, a

ScaleEntity Mesh, Scale, Scale, Scale

Return Mesh

End Function

' creates a texture from a pixmap
Function PixmapToTexture(pixmap:TPixmap, tex:TTexture)

If PixmapFormat(pixmap) <> PF_RGBA8888 Then pixmap = ConvertPixmap(pixmap, PF_RGBA8888)

' OpenB3D only function to copy pixmap to texture
'BufferToTex tex, PixmapPixelPtr(pixmap, 0, 0)

' MiniB3D workaround to copy pixmap to texture
glBindTexture (GL_TEXTURE_2D, tex.gltex[0])
gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, pixmap.width, pixmap.Height, GL_RGBA, GL_UNSIGNED_BYTE, PixmapPixelPtr(pixmap, 0, 0))

End Function

' --------------------------------------------------------------------------------
' Fixed BeginMax2D()
' --------------------------------------------------------------------------------
Function Begin2D()

Local x:Int, y:Int, w:Int, h:Int
GetViewport(x, y, w, h)

'glPopClientAttrib()
'glPopAttrib()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_COLOR)
glPopMatrix()

glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glDisable(GL_SCISSOR_TEST)
glDisable(GL_FOG)
glDisable(GL_CULL_FACE)

glMatrixMode GL_TEXTURE
glLoadIdentity

glMatrixMode GL_PROJECTION
glLoadIdentity
glOrtho 0, GraphicsWidth(), GraphicsHeight(), 0, -1, 1

glMatrixMode GL_MODELVIEW
glLoadIdentity

SetViewport x, y, w, h

Local MaxTex:Int
glGetIntegerv(GL_MAX_TEXTURE_UNITS, Varptr(MaxTex))

For Local Layer:Int = 0 Until MaxTex

glActiveTexture(GL_TEXTURE0+Layer)

glDisable(GL_TEXTURE_CUBE_MAP)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)

glDisable(GL_TEXTURE_2D)

Next

glActiveTexture(GL_TEXTURE0)

glViewport(0, 0, GraphicsWidth(), GraphicsHeight())
glScissor(0, 0, GraphicsWidth(), GraphicsHeight())

glEnable GL_BLEND
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_TEXTURE_2D)

End Function

' --------------------------------------------------------------------------------
' Fixed EndMax2D()
' --------------------------------------------------------------------------------
Function End2D()

' save the Max2D settings for later
'glPushAttrib(GL_ALL_ATTRIB_BITS)
'glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glMatrixMode(GL_COLOR)
glPushMatrix()

glDisable(GL_TEXTURE_CUBE_MAP)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)

glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)

TGlobal.EnableStates()
'glDisable(GL_TEXTURE_2D)

' ' only needed for OpenB3D
'TGlobal.alpha_enable[0] = 0 ' alpha blending was disabled by Max2d (GL_BLEND)
'TGlobal.blend_mode[0] = 1 ' force alpha blending
'TGlobal.fx1[0] = 0 ' full bright/surface normals was enabled by EnableStates (GL_NORMAL_ARRAY)
'TGlobal.fx2[0] = 1 ' vertex colors was enabled by EnableStates (GL_COLOR_ARRAY)

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR)
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE)

glClearDepth(1.0)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

glAlphaFunc(GL_GEQUAL, 0.5)

' ' only needed for OpenB3D
'For Local cam:TCamera=EachIn TCamera.cam_list

' active camera - was if cam.hide[0]=0
'If cam = TGlobal.camera_in_use

' fog with Max2d fix
' cam.UpdateFog()
' Exit

'EndIf

'Next

End Function


OpenB3D Version:
Code (blitzmax) Select
SuperStrict

Import openb3dmax.B3dglgraphics

Graphics3D DesktopWidth() / 1.5, DesktopHeight() / 1.5, 32, 2

ClearTextureFilters()

Global STARS:Int = 50000        ' number of stars per cluster part
Global DIMENSION:Int = 2000     ' cluster dimension
Global DISTRIBUTION:Float = 0.99' star distribution (0...1 = random...clustered)
Global SPEED:Float = 1.0        ' overall move speed multiplicator
Global BRAKE:Float = 0.95       ' brake multiplicator
Global VSYNC:Int = False        ' Vertical Sync
Global HOMING:Int = True        ' turn homing on/off
Global COLORED:Int = False      ' colorize clusters
Global TEXTURED:Int = True      ' use star texture
Global Scale:Float = 0.5        ' star scale factor
Global FADE:Int = True          ' fade distant clusters
Global INSTANCING:Int = True    ' duplicate source mesh

Global DEBUGLEVEL:Int = 0       ' 0=off, 1=no instancing, 2=colored, 3=scene

If DEBUGLEVEL > 0 Then

STARS = 1000
Scale = 1.6
DISTRIBUTION = 0.5
INSTANCING = False

If DEBUGLEVEL > 1 Then

COLORED = True
TEXTURED = False

If DEBUGLEVEL > 2 Then

HOMING = False
FADE = False

EndIf

EndIf

EndIf

' ----------------------------------------------------------------------------
' Global variables
' ----------------------------------------------------------------------------
Global localx:Float, localy:Float, localz:Float
Global globalx:Float, globaly:Float, globalz:Float
Global simx:Float, simy:Float, simz:Float
Global xspeed:Float, yspeed:Float, zspeed:Float, rspeed:Float
Global xadd:Float, yadd:Float, zadd:Float, radd:Float, tadd:Float

Global FPS:TFPS = New TFPS
Global MS:Int = MilliSecs()

' ----------------------------------------------------------------------------
' Init scene
' ----------------------------------------------------------------------------
Global masterpivot:TPivot = CreatePivot()

Global cam:TCamera = CreateCamera()
CameraRange cam, 1, DIMENSION * 8
PositionEntity cam, 0, 0, 0

Local tex:TTexture = CreateSunTexture(512, 128, 128, 128)
TextureBlend tex, 3

' ----------------------------------------------------------------------------
' Duplicate Quad mesh in a 9x9x9 matrix
' ----------------------------------------------------------------------------
Global meshlist:TList = CreateList()
Global quadlist:TList = CreateList()
Global quadsourcelist:TList = CreateList()
Global lx:Float, ly:Float, lz:Float

Local entities:TEntity[9, 9, 9]
Local meshes:TMesh[9, 9, 9]

Local mesh:TMesh

' star cluster properties
mesh = CreateMesh()
EntityFX mesh, 1 + 2
EntityBlend mesh, 3
If TEXTURED Then EntityTexture mesh, tex
HideEntity mesh

' create blueprint star cluster
For Local i:Int = 1 To STARS

CreateStar(mesh)

Next

' fix for OpenB3D to enable Texture Blending and Alpha?
SetBlend ALPHABLEND

' create 5x5x5 = 125 cluster
For Local x:Int = 0 To 4 Step 1

For Local y:Int = 0 To 4 Step 1

For Local z:Int = 0 To 4 Step 1

' create a mesh and duplicate its properties = FAST
If INSTANCING Then

entities[x, y, z] = CopyEntity(mesh)

PositionEntity entities[x, y, z], (x - 2) * DIMENSION, (y - 2) * DIMENSION, (z - 2) * DIMENSION

If COLORED Then

EntityColor entities[x, y, z], Rand(64, 255), Rand(64, 255), Rand(64, 255)

Else

EntityColor entities[x, y, z], 255, 255, 255

EndIf

ListAddLast(meshlist, entities[x, y, z])

' create many unique meshes without instancing = SLOW
Else

' new mesh
meshes[x, y, z] = CreateMesh()
EntityFX meshes[x, y, z], 1
EntityBlend meshes[x, y, z], 3
If TEXTURED Then

EntityTexture meshes[x, y, z], tex
EntityFX meshes[x, y, z], 1 + 2

EndIf

' duplicate blueprint cluster
For Local s:TQuad = EachIn quadsourcelist

Local q:TQuad = New TQuad

q.mesh = meshes[x, y, z]
q.x = s.x
q.y = s.y
q.z = s.z
q.scalex = s.scalex
q.scaley = s.scaley
q.RGB = s.RGB

q.Add()
q.Update(cam)

ListAddLast(quadlist, q)

Next

PositionEntity meshes[x, y, z], (x - 2) * DIMENSION, (y - 2) * DIMENSION, (z - 2) * DIMENSION

If COLORED Then

EntityColor meshes[x, y, z], Rand(128, 255), Rand(128, 255), Rand(128, 255)

Else

EntityColor meshes[x, y, z], 255, 255, 255

EndIf

ListAddLast(meshlist, meshes[x, y, z])

EndIf

RenderWorld

Flip False

Next

Next

Next

' Fix to really center the mouse (otherwise it "jumps" a little bit initially)
MoveMouse(GraphicsWidth() / 2, GraphicsHeight() / 2) ; Flip
MouseX()
MouseY()
MouseXSpeed()
MouseYSpeed()
Flip

MS = MilliSecs() - MS

' ----------------------------------------------------------------------------
' Main Loop
' ----------------------------------------------------------------------------
While Not AppTerminate()

If KeyHit(KEY_ESCAPE) Then End

' axis rotation
Local pitch:Float = Normalize(MouseY(), 0, GraphicsHeight() - 1, -2, 2)
Local yaw:Float = Normalize(MouseX(), 0, GraphicsWidth() - 1, 2, -2)
Local roll:Int = KeyDown(KEY_Q) - KeyDown(KEY_E)

' XYZ movement
Local xmove:Int = KeyDown(KEY_D) - KeyDown(KEY_A)
Local ymove:Int = KeyDown(KEY_R) - KeyDown(KEY_F)
Local zmove:Int = KeyDown(KEY_W) - KeyDown(KEY_S)
Local turbo:Int = KeyDown(KEY_TAB)

If xmove = 0 And xadd <> 0 Then xadd = 0 Else If xmove And xadd = 0 Then xadd = 0.001
If ymove = 0 And yadd <> 0 Then yadd = 0 Else If ymove And yadd = 0 Then yadd = 0.001
If zmove = 0 And zadd <> 0 Then zadd = 0 Else If zmove And zadd = 0 Then zadd = 0.001
If roll = 0 And radd <> 0 Then radd = 0 Else If roll And radd = 0 Then radd = 0.01
If turbo = 0 And tadd <> 0 Then tadd:*0.9 Else If turbo And tadd = 0 Then tadd = 0.1

If tadd < 0.01 Then tadd = 0.0

If Abs(xmove) Then xadd:+0.01
If Abs(ymove) Then yadd:+0.01
If Abs(zmove) Then zadd:+0.01
If Abs(roll) Then radd:+0.025
If Abs(turbo) Then tadd:+0.05

xadd:*1.025
yadd:*1.025
zadd:*1.025
radd:*1.025
tadd:*1.025

If xadd > 2.0 Then xadd = 2.0
If yadd > 2.0 Then yadd = 2.0
If zadd > 2.0 Then zadd = 2.0
If radd > 1.5 Then radd = 1.5
If tadd > 25.0 Then tadd = 25.0

' calculate movespeed/rollspeed
If Abs(xmove) Then xspeed = xadd * SPEED * Sgn(xmove) * (1.0 + tadd)
If Abs(ymove) Then yspeed = yadd * SPEED * Sgn(ymove) * (1.0 + tadd)
If Abs(zmove) Then zspeed = zadd * SPEED * Sgn(zmove) * (1.0 + tadd)
If Abs(roll) Then rspeed = radd * Sgn(roll)

' perform rotation and motion
Turn(cam, pitch, yaw, rspeed)
MoveEntity cam, xspeed, yspeed, zspeed

' keep camera at home
If HOMING Then Home(cam, masterpivot, DIMENSION)

' only the master instance quads are facing to the camera
For Local q:TQuad = EachIn quadlist

q.Update(cam)

Next

' fade mesh according to its distance
If FADE Then

For Local e:TEntity = EachIn meshlist

Local d:Float = EntityDistance(cam, e)
Local a:Float = Normalize(d, DIMENSION * 1.0, DIMENSION * 2.0, 1.0, 0.0)
EntityAlpha e, a

Next

EndIf

RenderWorld

FPS.Update()

' fix for OpenB3D to have vertex colors enabled (otherwise its greyscale)
Begin2D()

DrawText("FPS [Init]..: " + FPS.FPS + " [" + MS + "ms]", 0, 0)
DrawText("Star Cluster: " + STARS, 0, 15)
DrawText("Stars Total.: " + (STARS * 5 * 5 * 5), 0, 30)

End2D()

Flip VSYNC

' decrease speed
xspeed:*BRAKE
yspeed:*BRAKE
zspeed:*BRAKE
rspeed:*BRAKE

Wend

End

' creates a single star quad
Function CreateStar(mesh:TMesh, surf:TSurface = Null, xoff:Float = 0.0:Float, yoff:Float = 0.0, zoff:Float = 0.0)

Local q:TQuad = New TQuad

q.mesh = mesh
q.surf = surf

q.x = Rnd(-DIMENSION / 2, DIMENSION / 2) + xoff
q.y = Rnd(-DIMENSION / 2, DIMENSION / 2) + yoff
q.z = Rnd(-DIMENSION / 2, DIMENSION / 2) + zoff

' randomized clustering
If Rnd(1) > 1 - DISTRIBUTION Then

Local d:Float = Rnd(0.0001, 0.05)

q.x = lx + Rnd(-DIMENSION * d, DIMENSION * d) + xoff
q.y = ly + Rnd(-DIMENSION * d, DIMENSION * d) + yoff
q.z = lz + Rnd(-DIMENSION * d, DIMENSION * d) + zoff

EndIf

q.scalex = Rnd(1, Rnd(2, Rnd(4, Rnd(8, Rnd(16, 32))))) * Scale
q.scaley = q.scalex

Local c:Float = (1.0 / q.scalex)
If Rnd(1) > 0.9 And c < 0.70 Then c = 1 - c             ' 10% Giants
If c >= 0.0 And c < 0.35 Then q.RGB = [255, 64, 0]      ' 35% Class M
If c >= 0.35 And c < 0.50 Then q.RGB = [255, 128, 32]   ' 15% Class K
If c >= 0.50 And c < 0.60 Then q.RGB = [255, 255, 64]   ' 10% Class G
If c >= 0.60 And c < 0.70 Then q.RGB = [255, 255, 128]  ' 10% Class F
If c >= 0.70 And c < 0.80 Then q.RGB = [255, 255, 255]  ' 10% Class A
If c >= 0.80 And c < 0.90 Then q.RGB = [0, 64, 255]     ' 10% Class B
If c >= 0.90 And c <= 1.00 Then q.RGB = [0, 0, 255]     ' 10% Class O

q.Add()
q.Update(cam)

ListAddLast(quadlist, q)
ListAddLast(quadsourcelist, q)

lx = q.x
ly = q.y
lz = q.z

End Function

' keeps the player in a predefined cubic area
Function Home(target:TEntity, world:TEntity, homesize:Int = 100)

' store Local player Position
Local localx:Double = EntityX(target)
Local localy:Double = EntityY(target)
Local localz:Double = EntityZ(target)

' Check X axis
While localx > homesize

globalx:+homesize
localx:-homesize
PositionEntity target, localx, localy, localz
MoveEntity world, -homesize, 0, 0

Wend

While localx < - homesize

globalx:-homesize
localx:+homesize
PositionEntity target, localx, localy, localz
MoveEntity world, homesize, 0, 0

Wend

' Check Y axis
While localy > homesize

globaly:+homesize
localy:-homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, -homesize, 0

Wend

While localy < - homesize

globaly:-homesize
localy:+homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, homesize, 0

Wend

' Check Z axis
While localz > homesize

globalz:+homesize
localz:-homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, 0, -homesize

Wend

While localz < - homesize

globalz:-homesize
localz:+homesize
PositionEntity target, localx, localy, localz
MoveEntity world, 0, 0, homesize

Wend

' store simulated player Position
simx = localx + globalx
simy = localy + globaly
simz = localz + globalz

End Function

' ----------------------------------------------------------------------------
' Normalizes a value to given range
' ----------------------------------------------------------------------------
Function Normalize:Float(value:Float, vmin:Float, vmax:Float, nmin:Float, nmax:Float)

Return((value - vmin) / (vmax - vmin)) * (nmax - nmin) + nmin

End Function

' ----------------------------------------------------------------------------
' Turn Entity using Quaternions
' ----------------------------------------------------------------------------
Function Turn(Ent:TEntity, X:Float = 0.0, Y:Float = 0.0, Z:Float = 0.0, Glob:Int = False)

Local Pitch:Float = 0.0
Local Yaw:Float = 0.0
Local Roll:Float = 0.0

Local Quat:TQuaternion = EulerToQuat(0.0, 0.0, 0.0)
Local Turn_Quat:TQuaternion = EulerToQuat(0.0, 0.0, 0.0)

If Glob = False

Quat = EulerToQuat(EntityPitch(Ent, True), EntityYaw(Ent, True), EntityRoll(Ent, True))
Turn_Quat = EulerToQuat(X, Y, Z)
Quat = MultiplyQuats(Quat, Turn_Quat)
Quat = NormalizeQuat(Quat)
QuatToEuler2(Quat.x[0], Quat.y[0], Quat.z[0], Quat.w[0], pitch, yaw, roll)
RotateEntity Ent, pitch, yaw, roll

Else

RotateEntity Ent, EntityPitch(Ent) + X, EntityYaw(Ent) + Y, EntityRoll(Ent) + Z

EndIf

End Function

' ----------------------------------------------------------------------------
' Euler to Quaternion
' ----------------------------------------------------------------------------
Function EulerToQuat:TQuaternion(pitch:Float, yaw:Float, roll:Float)

Local cr:Float = Cos(-roll / 2.0)
Local cp:Float = Cos(pitch / 2.0)
Local cy:Float = Cos(yaw / 2.0)
Local sr:Float = Sin(-roll / 2.0)
Local sp:Float = Sin(pitch / 2.0)
Local sy:Float = Sin(yaw / 2.0)
Local cpcy:Float = cp * cy
Local spsy:Float = sp * sy
Local spcy:Float = sp * cy
Local cpsy:Float = cp * sy

Local q:TQuaternion = NewQuaternion()

q.w[0] = cr * cpcy + sr * spsy
q.x[0] = sr * cpcy - cr * spsy
q.y[0] = cr * spcy + sr * cpsy
q.z[0] = cr * cpsy - sr * spcy

Return q

End Function

' ----------------------------------------------------------------------------
' Quaternion to Euler
' ----------------------------------------------------------------------------
Function QuatToEuler2(x:Float, y:Float, z:Float, w:Float, pitch:Float Var, yaw:Float Var, roll:Float Var)

Local QuatToEulerAccuracy:Double = 1.0 / 2 ^ 31

Local sint:Float = (2.0 * w * y) - (2.0 * x * z)
Local cost_temp:Float = 1.0 - (sint * sint)
Local cost:Float

If Abs(cost_temp) > QuatToEulerAccuracy

cost = Sqr(cost_temp)

Else

cost = 0.0

EndIf

Local sinv:Float, cosv:Float, sinf:Float, cosf:Float

If Abs(cost) > QuatToEulerAccuracy

sinv = ((2.0 * y * z) + (2.0 * w * x)) / cost
cosv = (1.0 - (2.0 * x * x) - (2.0 * y * y)) / cost
sinf = ((2.0 * x * y) + (2.0 * w * z)) / cost
cosf = (1.0 - (2.0 * y * y) - (2.0 * z * z)) / cost

Else

sinv = (2.0 * w * x) - (2.0 * y * z)
cosv = 1.0 - (2.0 * x * x) - (2.0 * z * z)
sinf = 0.0
cosf = 1.0

EndIf

pitch = ATan2(sint, cost)
yaw = ATan2(sinf, cosf)
roll = -ATan2(sinv, cosv)

End Function

' ----------------------------------------------------------------------------
' Multiply Quaternion
' ----------------------------------------------------------------------------
Function MultiplyQuats:TQuaternion(q1:TQuaternion, q2:TQuaternion)

Local q:TQuaternion = NewQuaternion()

q.w[0] = q1.w[0] * q2.w[0] - q1.x[0] * q2.x[0] - q1.y[0] * q2.y[0] - q1.z[0] * q2.z[0]
q.x[0] = q1.w[0] * q2.x[0] + q1.x[0] * q2.w[0] + q1.y[0] * q2.z[0] - q1.z[0] * q2.y[0]
q.y[0] = q1.w[0] * q2.y[0] + q1.y[0] * q2.w[0] + q1.z[0] * q2.x[0] - q1.x[0] * q2.z[0]
q.z[0] = q1.w[0] * q2.z[0] + q1.z[0] * q2.w[0] + q1.x[0] * q2.y[0] - q1.y[0] * q2.x[0]

Return q

End Function

' ----------------------------------------------------------------------------
' Normalize Quaternion
' ----------------------------------------------------------------------------
Function NormalizeQuat:TQuaternion(q:TQuaternion)

Local uv:Float = Sqr(q.w[0] * q.w[0] + q.x[0] * q.x[0] + q.y[0] * q.y[0] + q.z[0] * q.z[0])

q.w[0] = q.w[0] / uv
q.x[0] = q.x[0] / uv
q.y[0] = q.y[0] / uv
q.z[0] = q.z[0] / uv

Return q

End Function

' ------------------------------------------------------------------------------------------------
' Quad Type
' ------------------------------------------------------------------------------------------------
Type TQuad

Field x:Float = 0.0 ' position x
Field y:Float = 0.0 ' position y
Field z:Float = 0.0 ' position z

Field row:Int = 0 ' texture row
Field col:Int = 0 ' texture column

Field scalex:Float = 1.0 ' current size X
Field scaley:Float = 1.0 ' current size Y

Field RGB:Int[] = [255, 255, 255] ' Vertex Color
Field Alpha:Float = 1.0 ' Vertex Alpha

Field v:Int = 0 ' vertex counter
Field mesh:TMesh = Null ' mesh pointer
Field surf:TSurface = Null ' surface pointer

' --------------------------------------------------------------------------------------------
' METHOD: Add new Quad
' --------------------------------------------------------------------------------------------
Method Add(col:Int = 0, row:Int = 0)

Local s:Int = CountSurfaces(mesh)
If s = 0 Then

surf = CreateSurface(mesh)

Else

surf = GetSurface(mesh, s)

EndIf

Local i:Int = CountVertices(surf)
If i + 4 > 32768 Then

surf = CreateSurface(mesh)
v = 0

EndIf

' add Vertices
Local V0:Int = AddVertex(surf, 0, 0, 0, 1, 0)
Local V1:Int = AddVertex(surf, 0, 0, 0, 1, 1)
Local V2:Int = AddVertex(surf, 0, 0, 0, 0, 1)
Local V3:Int = AddVertex(surf, 0, 0, 0, 0, 0)

' color vertices
VertexColor surf, V0, RGB[0], RGB[1], RGB[2], Alpha
VertexColor surf, V1, RGB[0], RGB[1], RGB[2], Alpha
VertexColor surf, V2, RGB[0], RGB[1], RGB[2], Alpha
VertexColor surf, V3, RGB[0], RGB[1], RGB[2], Alpha

' connect triangles
AddTriangle surf, V0, V1, V2
AddTriangle surf, V0, V2, V3

VertexTexCoords surf, V0, col + 1, row
VertexTexCoords surf, V1, col + 1, row + 1
VertexTexCoords surf, V2, col, row + 1
VertexTexCoords surf, V3, col, row

' increase vertex counter
If v >= 4 Then v = V0 + 4 Else v = V0

End Method

' --------------------------------------------------------------------------------------------
' METHOD: Update a Quad
' --------------------------------------------------------------------------------------------
Method Update(target:TEntity)

TFormVector scalex, 0, 0, target, Null
Local X1:Float = TFormedX()
Local Y1:Float = TFormedY()
Local Z1:Float = TFormedZ()

TFormVector 0, scaley, 0, target, Null
Local X2:Float = TFormedX()
Local Y2:Float = TFormedY()
Local Z2:Float = TFormedZ()

' set vertices
VertexCoords surf, v + 0, x - x1 - x2, y - y1 - y2, z - z1 - z2
VertexCoords surf, v + 1, x - x1 + x2, y - y1 + y2, z - z1 + z2
VertexCoords surf, v + 2, x + x1 + x2, y + y1 + y2, z + z1 + z2
VertexCoords surf, v + 3, x + x1 - x2, y + y1 - y2, z + z1 - z2

End Method

End Type

' ------------------------------------------------------------------------------------------------
' Exact FPS counter
' ------------------------------------------------------------------------------------------------

Type TFPS

Global renders:Int = 0

Field FPS:Int = 60 ' current FPS value
Field interval:Int = 999
Field averagefps:Int = 60 ' average FPS value
Field framedrops:Int = 0 ' dropped frames
Field oldlooptime:Int = 0
Field looptime:Int = 0
Field multi:Float ' main loop multiplicator

Field old:Int = MilliSecs()
Field totalfps:Int = 30
Field Ticks:Int

' --------------------------------------------------------------------------------------------
' METHOD: Update FPS counter
' --------------------------------------------------------------------------------------------
Method Update()

renders:+1

looptime = MilliSecs() - oldlooptime
oldlooptime = MilliSecs()

If MilliSecs() - old >= interval Then

old = MilliSecs()

FPS = renders * (1000 / interval)
renders = 0
totalfps:+FPS
Ticks:+1

averagefps = totalfps / Ticks

EndIf

End Method

End Type

' Create a stunning star texture
Function CreateSunTexture:TTexture(size:Int = 512, r:Int = 255, g:Int = 255, b:Int = 255)

Local i:Float, j:Int, col:Int, rgb:Int
Local x:Int, y:Int

Local w:Int = GraphicsWidth()

Local tempcam:TCamera, tempsun:TPivot
Local pixmap:TPixmap = CreatePixmap(size, size, PF_RGBA8888)
Local tex:TTexture = CreateTexture(size, size, 1 + 8)

For j = 0 To (size / 2) - 1

col = 255 - Normalize(j, 0, (size / 2.0) - 1, 0, 255)
If col > 255 Then col = 255
rgb = col * $1000000 + col * $10000 + col * $100 + col

For i = 0 To 360 Step 0.1

WritePixel(pixmap, (size / 2) + (Sin(i) * j), (size / 2) + (Cos(i) * j), rgb)

Next

Next

PixmapToTexture(pixmap, tex)
'SavePixmapPNG(pixmap, "proceduralstar1.png", 0)

' temp camera
tempcam = CreateCamera()
CameraRange tempcam, 1, w * 2

' fix for OpenB3D to simulate MiniB3D's Cameraviewport handling
CameraViewport tempcam, 0, GraphicsHeight() - size, size, size

' temp pivot
tempsun = CreatePivot()

' Create 4 body quads
CreateQuad(tempsun, size * 1.0, tex, 3, 1 + 8 + 16, r * 1.00, g * 1.00, b * 1.00, 1.00)
CreateQuad(tempsun, size * 1.5, tex, 3, 1 + 8 + 16, r * 1.00, g * 1.00, b * 1.00, 1.00)
CreateQuad(tempsun, size * 2.0, tex, 3, 1 + 8 + 16, r * 0.75, g * 0.75, b * 0.50, 0.75)
CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, r * 0.50, g * 0.50, b * 0.50, 0.50)

' create 4 ray quads
Local ray1:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 1)
Local ray2:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 1)
Local ray3:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 0.5)
Local ray4:TMesh = CreateQuad(tempsun, size * 4.0, tex, 3, 1 + 8 + 16, 255, 255, 255, 0.5)

' rescale and rotate rays
ScaleEntity ray1, size * 2, size / 32, size
ScaleEntity ray2, size / 32, size * 2, size
ScaleEntity ray3, size, size / 32, size
ScaleEntity ray4, size, size / 32, size
RotateEntity ray3, 0, 0, 45
RotateEntity ray4, 0, 0, 135

PositionEntity tempsun, 0, 0, w

' render star
RenderWorld
RenderWorld

' grab image
pixmap = GrabPixmap(0, 0, size, size)
For x = 0 To size - 1

For y = 0 To size - 1

rgb = ReadPixel(pixmap, x, y)

r = (rgb & $ff0000) / $10000
g = (rgb & $ff00) / $100
b = (rgb & $ff)

' rgb
rgb = (r + g + b) / 3 * $1000000 + r * $10000 + g * $100 + b

WritePixel(pixmap, x, y, rgb)

Next

Next

PixmapToTexture(pixmap, tex)
'SavePixmapPNG(pixmap, "proceduralstar2.png", 0)

' Delete pixmap, pivot and temp cam
pixmap = Null
FreeEntity tempsun
FreeEntity tempcam

Return tex

End Function

' custom quad creation
Function CreateQuad:Tmesh(parent:TEntity, scale:Float = 1.0, tex:TTexture = Null, blend:Int = False, fx:Int = False, r:Int = 255, g:Int = 255, b:Int = 255, a:Float = 1.0)

Local mesh:TMesh = CreateMesh()
Local surf:TSurface = CreateSurface(mesh)
Local v0:Int, v1:Int, v2:Int, v3:Int

v0 = AddVertex(surf, 1, 1, 0, 0, 0)
v1 = AddVertex(surf, -1, 1, 0, 1, 0)
v2 = AddVertex(surf, -1, -1, 0, 1, 1)
v3 = AddVertex(surf, 1, -1, 0, 0, 1)

AddTriangle surf, v0, v1, v2
AddTriangle surf, v0, v2, v3

If parent Then EntityParent Mesh, parent
If fx Then EntityFX Mesh, fx
If tex Then EntityTexture Mesh, tex
If blend Then EntityBlend Mesh, blend

EntityColor Mesh, r, g, b
EntityAlpha Mesh, a

VertexColor surf, v0, r, g, b, a
VertexColor surf, v1, r, g, b, a
VertexColor surf, v2, r, g, b, a
VertexColor surf, v3, r, g, b, a

ScaleEntity Mesh, Scale, Scale, Scale

Return Mesh

End Function

' creates a texture from a pixmap
Function PixmapToTexture(pixmap:TPixmap, tex:TTexture)

If PixmapFormat(pixmap) <> PF_RGBA8888 Then pixmap = ConvertPixmap(pixmap, PF_RGBA8888)

' OpenB3D only function to copy pixmap to texture
BufferToTex tex, PixmapPixelPtr(pixmap, 0, 0)

' MiniB3D workaround to copy pixmap to texture
'glBindTexture (GL_TEXTURE_2D, tex.gltex[0])
'gluBuild2DMipmaps(GL_TEXTURE_2D, GL_RGBA, w, h, GL_RGBA, GL_UNSIGNED_BYTE, PixmapPixelPtr(pixmap, 0, 0))

End Function

' --------------------------------------------------------------------------------
' Fixed BeginMax2D()
' --------------------------------------------------------------------------------
Function Begin2D()

Local x:Int, y:Int, w:Int, h:Int
GetViewport(x, y, w, h)

'glPopClientAttrib()
'glPopAttrib()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_COLOR)
glPopMatrix()

glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glDisable(GL_SCISSOR_TEST)
glDisable(GL_FOG)
glDisable(GL_CULL_FACE)

glMatrixMode GL_TEXTURE
glLoadIdentity

glMatrixMode GL_PROJECTION
glLoadIdentity
glOrtho 0, GraphicsWidth(), GraphicsHeight(), 0, -1, 1

glMatrixMode GL_MODELVIEW
glLoadIdentity

SetViewport x, y, w, h

Local MaxTex:Int
glGetIntegerv(GL_MAX_TEXTURE_UNITS, Varptr(MaxTex))

For Local Layer:Int = 0 Until MaxTex

glActiveTexture(GL_TEXTURE0+Layer)

glDisable(GL_TEXTURE_CUBE_MAP)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)

glDisable(GL_TEXTURE_2D)

Next

glActiveTexture(GL_TEXTURE0)

glViewport(0, 0, GraphicsWidth(), GraphicsHeight())
glScissor(0, 0, GraphicsWidth(), GraphicsHeight())

glEnable GL_BLEND
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_TEXTURE_2D)

End Function

' --------------------------------------------------------------------------------
' Fixed EndMax2D()
' --------------------------------------------------------------------------------
Function End2D()

' save the Max2D settings for later
'glPushAttrib(GL_ALL_ATTRIB_BITS)
'glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glMatrixMode(GL_COLOR)
glPushMatrix()

glDisable(GL_TEXTURE_CUBE_MAP)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)

glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)

TGlobal.EnableStates()
'glDisable(GL_TEXTURE_2D)

' only needed for OpenB3D
TGlobal.alpha_enable[0] = 0 ' alpha blending was disabled by Max2d (GL_BLEND)
TGlobal.blend_mode[0] = 1 ' force alpha blending
TGlobal.fx1[0] = 0 ' full bright/surface normals was enabled by EnableStates (GL_NORMAL_ARRAY)
TGlobal.fx2[0] = 1 ' vertex colors was enabled by EnableStates (GL_COLOR_ARRAY)

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR)
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE)

glClearDepth(1.0)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

glAlphaFunc(GL_GEQUAL, 0.5)

' only needed for OpenB3D
For Local cam:TCamera=EachIn TCamera.cam_list

' active camera - was if cam.hide[0]=0
If cam = TGlobal.camera_in_use

' fog with Max2d fix
cam.UpdateFog()
Exit

EndIf

Next

End Function
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

Flanker

Nice demo here, instancing is very powerful. You're not the only one using OpenB3D, I've totally abandon Blitz3D for OpenB3D because I prefer Blitzmax coding and because GLSL (or shaders in general) are amazing.

1) The only problem if I comment "SetBlend ALPHABLEND" is that there is no alpha when building the mesh at startup but after that it works well. However I also noticed a problem with alpha textures on my side a few weeks ago when I tried to code a 3D snow fall effect with OpenB3D. I had to use EntityAlpha to be able to activate texture alpha, wich is strange.

2) I remember using instancing with CopyEntity() in Blitz3D to make an "ocean" from one single animated mesh, and as you mentioned, in Blitz3D when you modify the source mesh, all instancies are also affected (useful to animate a lot of vertices). Maybe it's just not implemented here.

5) Funny to see that you also have a fix before the main loop to prevent the camera from spinning at the start. I think there is also a problem with MouseXSpeed()/MouseYSpeed() and MoveMouse(), or at least it works differently than Blitz3D.
Everyone knew it was impossible, until someone who didn't know made it.

Krischan

The mouse fix is needed both in MiniB3D and OpenB3D, but I don't know why this happens. And the two Flips before and after the Mouse Commands are important or it won't work. I had blending problems with OpenB3D since I first saw it - I created a few workarounds but this should be fixed in general IMHO. And yes, GLSL shaders are amazing with OpenB3D but a big problem with GLSL is that I hate to code without the possibility to debug values onscreen - you need tricks or hints to see what's wrong. But this is not a problem of OpenB3D ;D

Another problem I had already pointed out to mark is a strange bug in my game with reloaded Textures on B3D Models. In my game, when a new level is loaded, a BSP file is parsed and created as a mesh, gets textured and then I load a B3D model like a chest or a torch template and use CopyEntity to spread it across the level. After entering another level, all loaded assets are deleted and recreated. This worked until May 18 2017.

Now with the latest versions, either the models are completely untextured, I see only the normalmap or the normalmap+diffusemap in a random manner, but only on the B3D models - the level geometry is still fine. I've checked the OpenB3D source changes since the May 18 2017 version which is the last one where it worked (and it was the last one I could compile with my old BMK until he fixed this in the more current versions). I've sent my whole game project to Mark a few months ago and hope he will find some time to find the error as I failed with it.

But beside all these annoyances Blitzmax+OpenB3D is still a great combo and my first choice. On the other hand I spend more time to create workarounds in OpenB3D than creating progress in the game engine and game logic, which is not good.
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

Hi Krischan,

I'll just reply to point 8 for now, yes I'll be continuing development on Openb3dmax in my spare time, in fact I have been working on it these last few days and have an update to the 1.25 source coming, but I've a few nasty bugs to fix first, shaders and b3d files are crashing!

Well I'll more fully explain why development stopped, about the start of September I got sciatica in my lower back lifting about 20kg, so yes about 4 months ago, this is a recurring injury and I just lifted something too heavy. So I was in too much pain to concentrate for coding, now I still have sciatica quite bad but I've found ibuprofen gel which makes the pain manageable so I can do a bit of coding now.

I sorry you've had so many problems with Openb3d, it's always been a WIP but it is starting to mature into something like Blitz3d. It has great potential as Angros has done an awesome job on the OpenGL, all it needs is a bit more work, well quite a bit really!

Also sorry I never got back to you on LOF, I ran the exe but it just crashed when I launched a level. I'll have another look at it soon.

Krischan

Mark, your health is more important than our projects - don't worry, take the time you need and get well soon! I'll continue developing the game with the old April 30 version which seemed stable enough for me. I know that OpenB3D is not finished but it already creates stunning results. I/we really appreciate your work and if you get LOF running you can see what can be done with it by a single enthusiast (but I don't think I can already tap the full potential yet and my shaders are a mess). For the LOF crash: I've re-uploaded my complete Blitzmax+LOF environment and sent you a PM with some instructions how to setup. I think it makes sense to have the same basis here to hunt the bugs.

Beside that I found an interesting issue today:

9) I tried MSI Afterburner on LOF today - works fine until I switch with "Escape" into the game menu (which uses 2D) - the MSI overlay turns into filled quads and stays filled until I end+restart the game (see attached screenshots). This happens too when I replace my custom Begin2D()+End2D() functions with BeginMax2D()+EndMax2D(). Can somebody confirm this with another demo or could it be a bug in my game?
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

#5
Hi Krischan,

thanks for your consideration of my health, I am really not too bad so work will continue, albeit at a slow pace.

I have a fix up for the B3D textures not reloaded bug in LOF, it appears to have been the texture ref not getting returned from LoadTexture in TB3D, it's strange though as the ref *should* be the same and not need to be returned, anyway I'm pretty sure that was the problem.

Note that the latest version is now updated to Openb3d 1.25 so it will need LinkShader(s) called after AttachVertShader and AttachFragShader. The most interesting new feature is the PostFX functions which I'll need to make an example for. Also the GLES2 stuff, so it might build for Android now but I haven't tested.

Also I noticed your LOF shaders are written for GL3 which is what was crashing the game, so I just edited a few things to get it to compile on GL2.1:
//#version 130
//precision mediump float;
replace "in" with "varying"
replace "out" with "varying"


@Point 7: yes gltex[] was removed by Angros but I can add that back in to the streamed texture functions.

Krischan

Hi Mark,

great that you found time to add these fixes. It compiles and runs here, I've changed my shaders and added the LinkShader to the AssembleMap() Method in TBSP.bmx.

But when I switch the level it crashes at the ParseTextures() method - it seems to load the first texture but the next one crashes (in Renderworld() - inside the texture load function are some calls to UpdateLoader() which updates the Loading Screen with the spinning compass which needs a render pass in the loop there). This worked before and happens before the shaders are re-applied again.

I've added the patched Source, just overwrite the Dev project I've sent you.
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

#7
Hi Krischan,

thanks, I can't reproduce this so I'm guessing it's a shader crash, you should try reverting to your original GLSL 130 syntax. I haven't tried your patch yet so I'll do that now.

Krischan

It didn't work with the original shader, too. It only "works" (=no crash) if I uncomment my ApplyShaderCache() Method - but then I don't have any level textures anymore. My current level loader is quite complex, but as far as I remember it works like that (it's all in the source but I try to describe it with my own words):

Global Shader Variables in the Type:

Code (Blitzmax) Select
Field VERTEXSHADER_SOURCE:String
Field FRAGMENTSHADER_SOURCE:String
Field MAINSHADER:TShader
Field VERTEXSHADER_COMPILED:TShaderObject
Field FRAGMENTSHADER_COMPILED:TShaderObject
Field SHADERLIST:TList
Field SHADERCACHED:TList


The BSP level geometry consists of several meshes and these can have different diffuse textures while the doors share the same texture but doors must be additional single meshes (not part of the level geometry mesh) to make alpha / Z-Index work on them (you can actually see through some of them). As I have many doors but only some level geometry meshes, I try to reuse the door shader using a cache to load the level faster. But I use only ONE shader source for both use cases (geometry and doors).

I first load the fragment+vertex shader source once (VERTEXSHADER_SOURCE+FRAGMENTSHADER_SOURCE), create a shader material (MAINSHADER), compile both sources once and attach the compiled shaders (VERTEXSHADER_COMPILED+FRAGMENTSHADER_COMPILED) to the MAINSHADER material. With your additions I then use the new LinkShader command on the MAINSHADER material? Is this correct?

Code (Blitzmax) Select
s = ReadFile(PATH_SHADER + filename + ".vert")
VERTEXSHADER_SOURCE = ReadString(s, FileSize(PATH_SHADER + filename + ".vert"))
s = ReadFile(PATH_SHADER + filename + ".vert")
FRAGMENTSHADER_SOURCE = ReadString(s, FileSize(PATH_SHADER + filename + ".frag"))
MAINSHADER = CreateShaderMaterial("")
FRAGMENTSHADER_COMPILED = CreateFragShaderString(MAINSHADER, FRAGMENTSHADER_SOURCE)
VERTEXSHADER_COMPILED = CreateVertShaderString(MAINSHADER, VERTEXSHADER_SOURCE)
AttachFragShader(MAINSHADER, FRAGMENTSHADER_COMPILED)
AttachVertShader(MAINSHADER, VERTEXSHADER_COMPILED)
LinkShader(MAINSHADER)


The Shader cache works like this:
When a mesh gets assembled (geometry or door), on every new surface with different textures I create a new TShaderCache TMap Object and add all detail information about this surface to it (diffuse map, normal map, light map, surface id, mesh num and so on). I use a MD5 string of the diffuse filename plus the surface ID as a GUID key to distinguish between the same texture combinations and add new combinations to the SHADERLIST TList. This is because I don't want to create a new surface if it already exists. For example Walls should share the same shader while carpets or painting have their own shader. I think this is the best way to speed things up. And I've saved a lot of new surfaces with this algorithm.

After creating all meshes I use the ApplyShaderCache() Method. It iterates through all SHADERLIST objects and creates new shaders from the shader sources only for these surfaces. It adds the textures and variables to the shader and uses the ShadeSurface command to apply it to the cached surface. If it is a door, the MAINSHADER is applied instead.

I don't know why I don't compile this new created shader at this point (it is only created from the source) but I think I've had a big problem here I can't remember and needed to bypass it somehow (compiling here leads to a instant crash loading the initial level). I don't know if this is part of the problem:

Code (Blitzmax) Select
s.shader = CreateShader("", VERTEXSHADER_SOURCE, FRAGMENTSHADER_SOURCE)
ShaderTexture(s.shader, s.dm, "diffuseMap", 0)
ShaderTexture(s.shader, s.nm, "normalMap", 1)
ShaderTexture(s.shader, s.lm, "lightMap", 2)
...
ShadeSurface(s.surf, s.shader)


This new s.shader is then added to the SHADERCACHED TList for the next cache loop. The doors all use the precompiled global MAINSHADER instead of the newly created shaders. I don't know why but I had to create it this way but it really worked fast and flawless. When entering a new level, everything I created on the Load() Method is unloaded, cleared, nullified and destroyed. It is a large function but I think I didn't missed something and this worked before, too.

Currently I don't know what to change in my source to make it work again. So a very simple example would be nice: how to properly create, load, apply and unload/recreate a Vert+Frag Shader on a mesh with the same or a different texture without leaving shader, mesh or texture garbage behind before loading a new level - ideally with some kind of caching like I tried. A simple level loader/unloader with shaded meshes. I'm really confused how to use the shaders in such a complex but common scenario. But with such an example, I might be able to understand the problem better.
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

Hi Krischan,

well it's probably a bug in the Openb3d 1.25 code as the usematrix example is crashing, so  somethings wrong somewhere and I haven't figured it out yet, however your patch works perfectly here in win32 GL2.1. I suggest you test this by reverting to before the 1.25 update which is 12 sep 18.

markcwm

#10
Hi Krischan,

here's an update on my progress, still lots to do...

I added the missing tex.gltex[0], Angros renamed it to frames so you can use that too, gltex just points to frames. Edit: actually only working with anim textures but fixed in latest commit.

Also added is tex.no_mipmaps[0] but it's calculated rather than counted at generation like Minib3d.

BeginMax2D now has a version parameter 0 for old one, 1 for your latest code, which I've credited you for in the source. So thanks. Your version definitely works better with 2d text so it should really be the default.

Other changes are error messages if mesh or texture urls are bad, this is essential functionality, of course Blitz3d does this.

And the 3DS loader is fixed (edit: well nearly) it's a port from Openb3d but with a few changes, texture v's are flipped so words read from left-right, mesh transforms are enabled with MeshLoader "trans" and there's a second 3DS loader from Warner which is improved and basically should now work the same as the default one, it's enabled by MeshLoader "3ds2".

Then there's MeshDebug 1 to enable extra mesh debug info (edit: changed to MeshLoader "debug").

And then LightMesh ported from Blitz3d. And a fix for FreeSurface crashing.

Krischan

Thanks for the update - it builds but I can't compile my game anymore:

C:/Apps/Coding/BlitzMax/mod/openb3dmax.mod/openb3dmax.mod/openb3dmax.release.win32.x86.a(openb3dmax.bmx.release.win32.x86.o):(code+0x1ca5b): undefined reference to `Log2_'
Build Error: Failed to link C:/LOF/Legend of Faerghail.exe

I compiled it like I did with all previous versions, cross-tested it with the 30 apr 18 version (everything is fine) and the 12 sep 18 version works, too (still with the texture/model bug).
bmk makemods -a -r openb3dmax
bmk makemods -a -d openb3dmax

By the way, does LOF work for you now? Another suggestion for future versions: sometimes OpenB3D shows error messages, sometimes they are commented in the CPP sources. For example in texture.cpp all errors are commented while in file.cpp there are some outputs.

It would be nice if this would be uniform across all sources and that you could switch it on/off with a global command, like let's say ShowDebugErrors() / HideDebugErrors(). But this is only a nice-to-have feature.
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

Yes sorry, I forgot a file yesterday and didn't double-check.

The latest version replaces Log2 to Mipmaps. Also BeginMax2D now defaults to 1 which is your version. And another update to the 3DS loader, LoadMesh loads one mesh with many surfaces, LoadAnimMesh loads many meshes with one surface, CollapseAnimMesh was used before but wasn't working in bmx, it's a wierd function though so I'm not sure what it does. And shaders now debug errors when the filename is wrong.

Yes, LOF works with this latest commit so I'm not sure what's up but it may be a GL3 issue as I'm in GL2. You could try commenting out all ShadeSurface but I've just tested and that was working. Is it crashing exactly at the ShadeSurface function? It also might be the shader matrix code, so try running shader/bumpmap as it has some mat3 code.

Krischan

#13
First - the model texture bug has vanished now - so the model textures are applied correct again after loading another level. Great.

But it still crashes after loading the next level. It only "works" when I comment the two ShadeSurface() commands in the ApplyShaderCache() Method in include\types\TBSP.bmx. The level geometry is untextured then, well at least I have some distance fog ;-) So Shadesurface crashes the texture loader in the second run. But why? I've tested it on an old nVidia GTX 780M with a very outdated driver (398.82) and a brandnew GTX 2080 with the newest driver (417.71) which still has the same problem. So is it a bug in LOF or in OpenB3D? I don't know.

And I think my custom Begin/End commands are not complete yet as I'm still having the MSI Afterburner OSD bug after entering the menu. The bug vanishes when I uncomment the glActiveTexture(GL_TEXTURE0) in BeginMax2D(), but then all menu textures vanish, too. Does anybody know why or how to fix it? And it is risky to use my own functions in OpenB3D as I really didn't knew what excactly to do there - it is the result of try and error only.

So it would make sense if someone who knows more about OpenGL than I do would take a look at it.

Code (Blitzmax) Select

Rem
bbdoc: Blitz2D
EndRem
Type TBlitz2D

Function BeginMax2D( version:Int=1 )

Select version

Case 0 ' Old begin function (by Oddball)

glPopClientAttrib()
glPopAttrib()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_COLOR)
glPopMatrix()

Case 1 ' New begin function, allows instant resolution switch (by Krischan)

Local x:Int, y:Int, w:Int, h:Int
GetViewport(x, y, w, h)

'glPopClientAttrib()
'glPopAttrib()
glMatrixMode(GL_MODELVIEW)
glPopMatrix()
glMatrixMode(GL_PROJECTION)
glPopMatrix()
glMatrixMode(GL_TEXTURE)
glPopMatrix()
glMatrixMode(GL_COLOR)
glPopMatrix()

glDisable(GL_LIGHTING)
glDisable(GL_DEPTH_TEST)
glDisable(GL_SCISSOR_TEST)
glDisable(GL_FOG)
glDisable(GL_CULL_FACE)

glMatrixMode GL_TEXTURE
glLoadIdentity

glMatrixMode GL_PROJECTION
glLoadIdentity
glOrtho(0, TGlobal.width[0], TGlobal.height[0], 0, -1, 1) ' TGlobal.w/h instead of GraphicsW/H for MaxGUI

glMatrixMode GL_MODELVIEW
glLoadIdentity

SetViewport x, y, w, h

Local MaxTex:Int
glGetIntegerv(GL_MAX_TEXTURE_UNITS, Varptr(MaxTex))

For Local Layer:Int = 0 Until MaxTex
glActiveTexture(GL_TEXTURE0+Layer)

glDisable(GL_TEXTURE_CUBE_MAP)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)

glDisable(GL_TEXTURE_2D)
Next

glActiveTexture(GL_TEXTURE0)

glViewport(0, 0, TGlobal.width[0], TGlobal.height[0])
glScissor(0, 0, TGlobal.width[0], TGlobal.height[0])

glEnable GL_BLEND
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
glEnable(GL_TEXTURE_2D)

End Select

End Function

' Old end Max2d function
Function EndMax2D( version:Int=1 )

Select version

Case 0 ' Old end function (by Oddball)

' save the Max2D settings for later
glPushAttrib(GL_ALL_ATTRIB_BITS)
glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glMatrixMode(GL_COLOR)
glPushMatrix()

TGlobal.EnableStates() ' enables normals and vertex colors
glDisable(GL_TEXTURE_2D) ' needed as Draw in Max2d enables it, but doesn't disable after use

' set render state flags (crash if fx2 is not set)
TGlobal.alpha_enable[0]=0 ' alpha blending was disabled by Max2d (GL_BLEND)
TGlobal.blend_mode[0]=1 ' force alpha blending
TGlobal.fx1[0]=0 ' full bright/surface normals was enabled by EnableStates (GL_NORMAL_ARRAY)
TGlobal.fx2[0]=1 ' vertex colors was enabled by EnableStates (GL_COLOR_ARRAY)

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL,GL_SEPARATE_SPECULAR_COLOR)
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE)

glClearDepth(1.0)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

glAlphaFunc(GL_GEQUAL,0.5)

For Local cam:TCamera=EachIn TCamera.cam_list
If cam=TGlobal.camera_in_use ' active camera - was if cam.hide[0]=0
cam.UpdateFog() ' fog with Max2d fix
Exit
EndIf
Next

Case 1 ' New end function, allows instant resolution switch (by Krischan)

' save the Max2D settings for later
'glPushAttrib(GL_ALL_ATTRIB_BITS)
'glPushClientAttrib(GL_CLIENT_ALL_ATTRIB_BITS)
glMatrixMode(GL_MODELVIEW)
glPushMatrix()
glMatrixMode(GL_PROJECTION)
glPushMatrix()
glMatrixMode(GL_TEXTURE)
glPushMatrix()
glMatrixMode(GL_COLOR)
glPushMatrix()
 
glDisable(GL_TEXTURE_CUBE_MAP)
glDisable(GL_TEXTURE_GEN_S)
glDisable(GL_TEXTURE_GEN_T)
glDisable(GL_TEXTURE_GEN_R)

glDisable(GL_TEXTURE_2D)
glDisable(GL_BLEND)

TGlobal.EnableStates()

' set render state flags (crash if fx2 is not set)
TGlobal.alpha_enable[0]=0 ' alpha blending was disabled by Max2d (GL_BLEND)
TGlobal.blend_mode[0]=1 ' force alpha blending
TGlobal.fx1[0]=0 ' full bright/surface normals was enabled by EnableStates (GL_NORMAL_ARRAY)
TGlobal.fx2[0]=1 ' vertex colors was enabled by EnableStates (GL_COLOR_ARRAY)

glLightModeli(GL_LIGHT_MODEL_COLOR_CONTROL, GL_SEPARATE_SPECULAR_COLOR)
glLightModeli(GL_LIGHT_MODEL_LOCAL_VIEWER,GL_TRUE)

glClearDepth(1.0)
glDepthFunc(GL_LEQUAL)
glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST)

glAlphaFunc(GL_GEQUAL, 0.5)

For Local cam:TCamera=EachIn TCamera.cam_list ' active camera - was if cam.hide[0]=0
If cam = TGlobal.camera_in_use ' fog with Max2d fix
cam.UpdateFog()
Exit
EndIf
Next

End Select

End Function

End Type
Kind regards
Krischan

Windows 10 Pro | i7 9700K@ 3.6GHz | RTX 2080 8GB]
Metaverse | Blitzbasic Archive | My Github projects

markcwm

#14
Hi Krischan,

I think I have a fix for the ShadeSurface crash now, I was able to reproduce it, as it turns out the shader/usematrix crash is the same bug, which is a crash if a b3d model had no texture, it happened in shader render TurnOn. The fix skips rendering if there's no texture for shader, brush and surface brush.

Then there's a fix for the vertex color breaking with Max2D, it happens if the mesh is the last in the list, some state is not being set/reset but I couldn't figure out what so I settled for just rendering a dummy mesh after everything.

Also BeginMax2d has the glPopAttrib/glPushAttrib code back in because this may fix your MSI Afterburner issue, if you don't do this the blend state isn't saved and can be affected by other code, Text doesn't do this and it changes depending on the last blend used, see standard/alphamap. I wasn't able to test with Afterburner as I don't know what that is, so please let me know how that goes. Thanks!

I also noticed in LOF when you reload a level sometimes a letter is blocked out in the menu text, like it was a C today but an S last time, I think it must be a LOF bug as it happens randomly.