2 std/tables, std/sequtils, std/strutils,
3 sdl2, opengl, thirdparty/imgui, thirdparty/stb_image,
10 winDefaultX* = SDL_WINDOWPOS_CENTERED
11 winDefaultY* = SDL_WINDOWPOS_CENTERED
14 winFlags = SDL_WINDOW_OPENGL or SDL_WINDOW_RESIZABLE or SDL_WINDOW_SHOWN
17 waterTextureNames* = ["_water_0", "_water_1", "_water_2"]
18 waterTextureColors*: array[3, DFColor] = [
19 (r: 0'u8, g: 0'u8, b: 255'u8, a: 128'u8),
20 (r: 0'u8, g: 255'u8, b: 0'u8, a: 128'u8),
21 (r: 255'u8, g: 0'u8, b: 0'u8, a: 128'u8)
26 colBlack* = initDFColor(0x00, 0x00, 0x00, 0xFF)
27 colWhite* = initDFColor(0xFF, 0xFF, 0xFF, 0xFF)
28 colGrey* = initDFColor(200, 200, 200, 0xFF)
29 colDkGrey* = initDFColor(100, 100, 100, 0xFF)
30 colRed* = initDFColor(0xFF, 0x00, 0x00, 0xFF)
31 colBlue* = initDFColor(0x00, 0x00, 0xFF, 0xFF)
34 Texture* = ref object of RootObj
39 frames*: int # this is used for informational purposes only
42 sdlWindow: WindowPtr = nil
43 glContext: GlContextPtr = nil
44 textures: Table[string, Texture] = initTable[string, Texture]()
46 proc isTexture*(data: string): bool =
47 var ix, iy, icomp: int
48 result = stb_image.infoFromMemory(data, ix, iy, icomp)
50 proc isAnimTexture*(ar: FsArchive): bool =
51 (ar.findFile("TEXT/ANIM") != nil)
53 proc isAnimTexture*(stream: Stream): bool =
54 let pos = stream.getPosition()
55 var ar = vfs.openArchive("animtex", stream)
57 result = ar.isAnimTexture()
59 stream.setPosition(pos)
63 proc isAnimTexture*(data: string): bool =
64 var stream = newStringStream(data)
65 result = isAnimTexture(stream)
68 proc newTexture(): Texture =
73 glGenTextures(1, result.glTex.addr)
74 glBindTexture(GL_TEXTURE_2D, result.glTex)
75 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST)
76 glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST)
77 glBindTexture(GL_TEXTURE_2D, 0)
79 proc newFillTexture(name: string, size: DFSize, color: DFColor): Texture =
83 result.glType = GL_RGBA
84 var buf = color.repeat(size.w * size.h)
85 glBindTexture(GL_TEXTURE_2D, result.glTex)
86 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA.GLint, size.w.GLsizei, size.h.GLsizei, 0, GL_RGBA, GL_UNSIGNED_BYTE, buf[0].addr)
87 glBindTexture(GL_TEXTURE_2D, 0)
89 proc newCheckersTexture*(name: string, size: DFSize, color1: DFColor = colWhite, color2: DFColor = colGrey): Texture =
93 result.glType = GL_RGB
94 var buf = newSeq[tuple[r, g, b: uint8]](result.size.w * result.size.h)
95 for i, c in buf.mpairs():
96 let x = i mod result.size.w
97 let y = i div result.size.w
98 let rx = x mod (checkerSize * 2)
99 let ry = y mod (checkerSize * 2)
100 if (rx >= checkerSize) xor (ry >= checkerSize):
108 glBindTexture(GL_TEXTURE_2D, result.glTex)
109 glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB.GLint, result.size.w.GLsizei, result.size.h.GLsizei, 0, GL_RGB, GL_UNSIGNED_BYTE, buf[0].addr)
110 glBindTexture(GL_TEXTURE_2D, 0)
112 proc newErrorTexture(name: string): Texture =
113 result = newCheckersTexture(name, initDFSize(16, 16), colBlack, initDFColor(0xFF, 0x00, 0xFF, 0xFF))
115 proc newTexture*(name: string, contents: string, overrideSize: DFSize = initDFSize(0, 0)): Texture =
116 var ix, iy, icomp: int
117 if not stb_image.infoFromMemory(contents, ix, iy, icomp):
118 error("Could not read texture: ", name, " ", stb_image.failureReason())
121 result = newTexture()
124 let wantComp = (if icomp == 4: 4 else: 3)
125 var data = stb_image.loadFromMemory(contents, ix, iy, icomp, wantComp)
126 var dataPtr: pointer = nil
127 if (overrideSize.w > 0 and overrideSize.w < ix) or (overrideSize.h > 0 and overrideSize.h < iy):
128 var newSize = overrideSize
129 if newSize.w <= 0 or newSize.w > ix: newSize.w = ix
130 if newSize.h <= 0 or newSize.h > iy: newSize.h = iy
131 # cut out the top left part of the texture
132 let dstStride = newSize.w * wantComp
133 let srcStride = ix * wantComp
134 var buf = newString(newSize.w * newSize.h * wantComp + 1)
137 for y in 0 ..< newSize.h:
138 copyMem(buf[dst].addr, data[src].addr, dstStride)
141 result.size = newSize
142 dataPtr = buf[0].addr
144 result.size = initDFSize(ix, iy)
145 dataPtr = data[0].addr
147 result.glType = (if wantComp == 4: GL_RGBA else: GL_RGB)
148 glBindTexture(GL_TEXTURE_2D, result.glTex)
149 glTexImage2D(GL_TEXTURE_2D, 0, result.glType.GLint, result.size.w.GLsizei, result.size.h.GLsizei, 0, result.glType, GL_UNSIGNED_BYTE, dataPtr)
150 glBindTexture(GL_TEXTURE_2D, 0)
152 proc newAnimTexture*(name: string, stream: Stream): Texture =
155 var ar = vfs.openArchive(name, stream)
157 error("Could not load anim texture: ", name, " (file is not an archive)")
162 var f = ar.readFile("TEXT/ANIM")
164 error("Could not load anim texture: ", name, " (file doesn't have ANIM)")
167 var info = f.readDFConfig()
170 if not ("resource" in info and "framewidth" in info and "frameheight" in info):
171 error("Could not load anim texture: ", name, " (file has invalid ANIM)")
174 let resName = "TEXTURES/" & info["resource"]
175 f = ar.readFile(resName)
177 error("Could not load anim texture: ", name, " (missing actual texture: ", resName, ")")
180 let contents = f.readAll()
183 let overrideSize = initDFSize(info["framewidth"].parseInt(), info["frameheight"].parseInt())
185 result = newTexture(name, contents, overrideSize)
187 if result != nil and "framecount" in info:
188 result.frames = info["framecount"].parseInt()
190 proc newTexture*(path: string, overrideSize: DFSize = (0, 0)): Texture =
191 var f = vfs.open(path, false)
193 error("Could not find texture: ", path)
195 if f.isAnimTexture():
196 result = newAnimTexture(path, f)
198 let contents = f.readAll()
199 result = newTexture(path, contents, overrideSize)
202 proc deinit*(self: Texture) =
204 glDeleteTextures(1, self.glTex.addr)
207 proc destroy*(self: Texture) =
208 assert(self.path in textures, "orphaned texture: " & self.path)
209 textures.del(self.path)
212 proc destroyTexture*(name: string): bool {.discardable.} =
214 textures[name].destroy()
219 proc loadTexture*(name, contents: string): Texture {.discardable.} =
220 result = newTexture(name, contents)
223 textures[name].deinit()
224 textures[name] = result
226 proc loadTexture*(path: string): Texture {.discardable.} =
227 result = newTexture(path)
230 textures[path].deinit()
231 textures[path] = result
233 proc loadTextureExt*(path: string, name: string, overrideSize: DFSize = (0, 0)): Texture {.discardable.} =
234 result = newTexture(path, overrideSize)
238 textures[name].deinit()
239 textures[name] = result
241 proc getTexture*(name: string): Texture =
243 return textures[name]
244 result = loadTexture(name)
249 for tex in textures.mvalues():
256 discard sdlWindow.glMakeCurrent(nil)
257 sdl2.glDeleteContext(glContext)
264 if not sdl2.init(INIT_VIDEO or INIT_EVENTS):
265 error("Could not init SDL2: ", sdl2.getError())
268 addExitProc(gfx.shutdown)
270 sdlWindow = sdl2.createWindow("maped", winDefaultX, winDefaultY, winDefaultW, winDefaultH, winFlags)
272 error("Could not create SDL2 window: ", sdl2.getError())
275 # the "opengl 2.1" backend of imgui actually only uses 1.x features
276 discard sdl2.glSetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 1)
277 discard sdl2.glSetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 3)
278 discard sdl2.glSetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_COMPATIBILITY)
279 glContext = sdlWindow.glCreateContext()
281 error("Could not create SDL2 GL2.1 context: ", sdl2.getError())
284 discard sdl2.glSetSwapInterval(1)
286 opengl.loadExtensions()
288 if not imgui.init(sdlWindow):
289 error("Could not init ImGui")
292 # spawn special textures
293 textures["$$ERROR"] = newErrorTexture("$$ERROR")
294 for i, x in waterTextureNames:
295 textures[x] = newFillTexture(x, initDFSize(1, 1), waterTextureColors[i])
297 glDisable(GL_DEPTH_TEST)
298 glDisable(GL_ALPHA_TEST)
299 glDisable(GL_CULL_FACE)
300 glDisable(GL_STENCIL_TEST)
301 glDisable(GL_LIGHTING)
302 glDepthMask(GL_FALSE)
304 proc beginFrame*(): bool =
306 var event = sdl2.defaultEvent
307 while sdl2.pollEvent(event):
308 discard imgui.event(event)
309 if event.kind == QuitEvent:
312 glDisable(GL_SCISSOR_TEST)
313 glClearColor(0, 0, 0, 0xFF)
314 glClear(GL_COLOR_BUFFER_BIT)
315 glEnable(GL_SCISSOR_TEST)
321 sdlWindow.glSwapWindow()
323 proc vidWidth*(): int = sdlWindow.getSize()[0]
325 proc vidHeight*(): int = sdlWindow.getSize()[1]
327 proc vidSize*(): DFSize =
328 let s = sdlWindow.getSize()
329 result = (s[0].int, s[1].int)
331 template withBlending(on: bool, actions: untyped): untyped =
334 glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)
339 template withTexture(tex: GLuint, actions: untyped): untyped =
341 glDisable(GL_TEXTURE_2D)
343 glEnable(GL_TEXTURE_2D)
344 glBindTexture(GL_TEXTURE_2D, tex)
346 glBindTexture(GL_TEXTURE_2D, 0.GLuint)
348 proc drawQuad*(pos: DFPoint, size: DFSize, tint: DFColor = colWhite) =
351 let x1 = (pos.x + size.w).GLint
352 let y1 = (pos.y + size.h).GLint
353 glColor4ub(tint.r, tint.g, tint.b, tint.a)
355 withBlending(tint.a != 0xFF'u8):
356 glBegin(GL_TRIANGLE_FAN)
363 proc drawQuad*(pos: DFPoint, size: DFSize, tex: Texture, tint: DFColor = colWhite, flipX: bool = false) =
364 let xn = ((if flipX: -size.w else: size.w) div tex.size.w).GLint
365 let yn = (size.h div tex.size.h).GLint
368 let x1 = (pos.x + size.w).GLint
369 let y1 = (pos.y + size.h).GLint
370 let blend = (tex.glType == GL_RGBA or tint.a != 0xFF'u8)
371 glColor4ub(tint.r, tint.g, tint.b, tint.a)
372 withTexture(tex.glTex):
374 glBegin(GL_TRIANGLE_FAN)
385 proc correctLine(x1, y1, x2, y2: var GLint) =
386 # make lines only top-left/bottom-right and top-right/bottom-left
397 proc drawRect*(pos: DFPoint, size: DFSize, tint: DFColor) =
400 var x2 = (pos.x + size.w).GLint
401 var y2 = (pos.y + size.h).GLint
402 var nx1, ny1, nx2, ny2: GLint
403 let blend = (tint.a != 0xFF'u8)
404 if x1 > x2: swap(x1, x2)
405 if y1 > y2: swap(y1, y2)
407 glColor4ub(tint.r, tint.g, tint.b, tint.a)
411 (nx1, ny1, nx2, ny2) = (x1, y1, x2, y1)
412 correctLine(nx1, ny1, nx2, ny2)
415 (nx1, ny1, nx2, ny2) = (x2, y1, x2, y2)
416 correctLine(nx1, ny1, nx2, ny2)
419 (nx1, ny1, nx2, ny2) = (x2, y2, x1, y2)
420 correctLine(nx1, ny1, nx2, ny2)
423 (nx1, ny1, nx2, ny2) = (x1, y2, x1, y1)
424 correctLine(nx1, ny1, nx2, ny2)
429 proc drawPoints*(pts: openArray[DFPoint], tint: DFColor, size: int = 1) =
430 glPointSize(size.GLfloat)
431 glColor4ub(tint.r, tint.g, tint.b, tint.a)
433 withBlending(tint.a != 0xFF):
436 glVertex2i(pt.x.GLint, pt.y.GLint)
439 proc drawPoint*(pt: DFPoint, tint: DFColor, size: int = 1) =
440 glPointSize(size.GLfloat)
441 glColor4ub(tint.r, tint.g, tint.b, tint.a)
443 withBlending(tint.a != 0xFF):
445 glVertex2i(pt.x.GLint, pt.y.GLint)
448 proc setViewport*(pos: DFPoint, size: DFSize) =
449 let y = GLint(vidHeight() - pos.y - size.h)
450 glScissor(pos.x.GLint, y, size.w.GLsizei, size.h.GLsizei)
451 glViewport(pos.x.GLint, y, size.w.GLsizei, size.h.GLsizei)
453 glMatrixMode(GL_PROJECTION)
455 glOrtho(0f, size.w.GLfloat, size.h.GLfloat, 0f, -1f, +1f)
457 glMatrixMode(GL_MODELVIEW)
460 proc setOffset*(pos: DFPoint) =
461 glMatrixMode(GL_MODELVIEW)
462 if pos.x == 0 and pos.y == 0:
465 glTranslatef(-pos.x.GLfloat, -pos.y.GLfloat, 0f)
467 proc clear*(c: DFColor) =
468 glClearColor(c.r.GLfloat / 255f, c.g.GLfloat / 255f, c.b.GLfloat / 255f, 1f)
469 glClear(GL_COLOR_BUFFER_BIT)