Compare commits
15 commits
6794b35978
...
45cc6dac00
Author | SHA1 | Date | |
---|---|---|---|
45cc6dac00 | |||
fc39a93a94 | |||
11ef0fbd41 | |||
b4c7061fc9 | |||
6d6c807ff2 | |||
8155c53314 | |||
67307032da | |||
0192db3f67 | |||
12940dad1d | |||
e0b02d2708 | |||
bb4f8e1c00 | |||
2caf78c88a | |||
893208f4c2 | |||
080d9bcc67 | |||
6212b816f0 |
28 changed files with 952 additions and 160 deletions
|
@ -44,6 +44,7 @@
|
|||
|
||||
{.hint[ConvFromXtoItselfNotNeeded]:off.}
|
||||
|
||||
import std/tables
|
||||
import arr_ref # for SliceMem
|
||||
|
||||
type LoadableResourceStatus* = enum
|
||||
|
@ -236,6 +237,19 @@ func escapeUTF8*(s: string): string =
|
|||
|
||||
# TODO: automatically disable threads when not in main thread
|
||||
|
||||
type ProtocolHandler* = proc(uri: string): proc(self: LoadableResource)
|
||||
var custom_protocol_handlers: Table[string, ProtocolHandler]
|
||||
var log_uri_handler: proc(uri: string)
|
||||
|
||||
proc registerCustomProtocol*(prefix: string, handler: ProtocolHandler) =
|
||||
## Registers a handler for a custom protocol. The function will be run for
|
||||
## each uri that is requested, and will return a start_func, which in turn
|
||||
## will call self.onload()
|
||||
custom_protocol_handlers[prefix] = handler
|
||||
|
||||
proc registerLogUriHandler*(handler: proc(uri: string)) =
|
||||
log_uri_handler = handler
|
||||
|
||||
proc loadUri*(
|
||||
uri: string,
|
||||
onload_func: proc(ok: bool, err: string, data: SliceMem[byte]) = nil,
|
||||
|
@ -244,7 +258,17 @@ proc loadUri*(
|
|||
use_threads = true,
|
||||
): Fetch {.discardable.} =
|
||||
|
||||
echo "fetching ", uri
|
||||
if log_uri_handler != nil:
|
||||
log_uri_handler(uri)
|
||||
|
||||
for k,v in custom_protocol_handlers:
|
||||
if uri.startswith k:
|
||||
proc str(): string = uri
|
||||
var self = newLoadableResource[Fetch](v(uri), str, false)
|
||||
self.onload_func = onload_func
|
||||
if auto_start:
|
||||
start(self)
|
||||
return self
|
||||
|
||||
var start_func: proc(self: LoadableResource)
|
||||
var self: Fetch
|
||||
|
@ -318,3 +342,4 @@ proc loadUri*(
|
|||
if auto_start:
|
||||
start(self)
|
||||
return self
|
||||
|
||||
|
|
|
@ -39,6 +39,8 @@ import std/strformat
|
|||
import ./util
|
||||
|
||||
func size*(dtype: DataType): int16 =
|
||||
## Size of a DataType in bytes
|
||||
|
||||
assert dtype != Unknown, "Can't use unknown type"
|
||||
case dtype:
|
||||
of Float, Int, UInt: 4
|
||||
|
@ -47,14 +49,14 @@ func size*(dtype: DataType): int16 =
|
|||
of Unknown: 0
|
||||
|
||||
func stride*(layout: AttributeList, align: int32 = 4): int32 =
|
||||
# Stride of an attribute list, aligned to a byte boundary (default 4)
|
||||
## Stride of an attribute list, aligned to a byte boundary (default 4)
|
||||
for a in layout:
|
||||
result = max(result, a.offset + a.dtype.size * a.count)
|
||||
result = align(result, align)
|
||||
|
||||
proc add*(layout: var AttributeList, attr: Attribute) =
|
||||
# Adds an attribute to a seq setting the approriate location and offset
|
||||
# (aligned to a 4 byte boundary)
|
||||
## Adds an attribute to a seq setting the approriate location and offset
|
||||
## (aligned to a 4 byte boundary)
|
||||
var attr = attr
|
||||
if layout.len != 0:
|
||||
let last = layout[layout.high]
|
||||
|
@ -67,11 +69,13 @@ proc add*(layout: var AttributeList, attr: Attribute) =
|
|||
layout[^1] = attr
|
||||
|
||||
proc contains*(layout: AttributeList, name: string): bool =
|
||||
## Returns whether an attribute with name `name` exists.
|
||||
for attr in layout:
|
||||
if attr.name == name:
|
||||
return true
|
||||
|
||||
proc `[]`*(layout: AttributeList, name: string): Attribute =
|
||||
## Gets an attribute from a seq of attributes from its name.
|
||||
for attr in layout:
|
||||
if attr.name == name:
|
||||
return attr
|
||||
|
|
|
@ -97,5 +97,7 @@ when defined(android):
|
|||
export platform.myouAndroidGetJniEnv
|
||||
export platform.myouAndroidGetInternalDataPath
|
||||
export platform.myouAndroidGetExternalDataPath
|
||||
export platform.myouAndroidAPKFilePointerLength
|
||||
export platform.myouCloseMobileApp
|
||||
|
||||
export platform.myouSetKeyboardVisible
|
||||
|
|
|
@ -138,7 +138,7 @@ proc loadFileFromSlices*(tex: Texture, slices: seq[SliceMem[byte]],
|
|||
|
||||
when not defined(nimdoc):
|
||||
assert tex.tex_type != TexCube, "Loading a cube texture from file is not supported yet"
|
||||
let format = if min_channels == 0:
|
||||
let format = if min_channels <= tex.format.channel_count:
|
||||
tex.format
|
||||
else:
|
||||
tex.format.resize(min_channels)
|
||||
|
|
|
@ -68,14 +68,22 @@ when defined(nimdoc):
|
|||
type TYPES* = CacheSettings | EncodingSpeed | RgbBcFmt | BlockSize
|
||||
|
||||
when defined(android) or defined(ios) or defined(emscripten):
|
||||
template has_bptc_support: bool = gl.GLAD_GL_EXT_texture_compression_bptc
|
||||
template has_bptc_support: bool = gl.GLAD_GL_EXT_texture_compression_bptc and not defined(myouForceAstc)
|
||||
else:
|
||||
template has_bptc_support: bool = gl.GLAD_GL_ARB_texture_compression_bptc
|
||||
template has_bptc_support: bool = gl.GLAD_GL_ARB_texture_compression_bptc and not defined(myouForceAstc)
|
||||
template has_astc_support: bool = gl.GLAD_GL_OES_texture_compression_astc or
|
||||
gl.GLAD_GL_KHR_texture_compression_astc_ldr
|
||||
gl.GLAD_GL_KHR_texture_compression_astc_ldr or
|
||||
defined(ios) or
|
||||
defined(myouForceAstc)
|
||||
|
||||
const myouMinTextureChannels {.intdefine.} = 0
|
||||
const myouEngineNumTextureThreads {.intdefine.} = 4
|
||||
const myouBC7VerboseMode {.booldefine.} = false
|
||||
const myouAllCacheFilesExist {.booldefine.} = false
|
||||
const myouLoadUncompressedTextures {.booldefine.} = false
|
||||
when defined(myouAllCacheFilesExist):
|
||||
import ../platform/platform
|
||||
import std/strutils
|
||||
|
||||
template u32(x: untyped): uint32 = cast[uint32](x)
|
||||
|
||||
|
@ -90,7 +98,7 @@ template block_file_size(width, height, depth, block_x, block_y, block_z, block_
|
|||
let blocks_z = (depth.int + block_z.int - 1) div block_z.int
|
||||
blocks_x * blocks_y * blocks_z * block_byte_size.int
|
||||
|
||||
proc make_mipmaps(tex: Texture, pixels: SliceMem[byte], compress: CompressMipmap): (seq[KtxPart], seq[SliceMem[byte]]) =
|
||||
proc make_mipmaps(tex: Texture, pixels: SliceMem[byte], compress: CompressMipmap, fmt_debug: string): (seq[KtxPart], seq[SliceMem[byte]]) =
|
||||
var width = tex.width.int32
|
||||
var height = tex.height.int32
|
||||
let depth = tex.format_depth
|
||||
|
@ -147,7 +155,7 @@ proc make_mipmaps(tex: Texture, pixels: SliceMem[byte], compress: CompressMipmap
|
|||
h = max(1, h shr 1)
|
||||
mip_level.inc
|
||||
let time2 = getmonotime().ticks.float/1000000000
|
||||
echo "time: ", time2-time, " ", tex.name
|
||||
echo "time: ", time2-time, " ", tex.name, " ", fmt_debug
|
||||
return (parts, data_refs)
|
||||
|
||||
when defined(myouUseBC7Encoder):
|
||||
|
@ -183,7 +191,7 @@ when defined(myouUseBC7Encoder):
|
|||
assert err == NO_ERROR, "bc7enc error: " & $err
|
||||
return (data, row_len)
|
||||
|
||||
let (parts, data_refs) = make_mipmaps(tex, pixels, compress)
|
||||
let (parts, data_refs) = make_mipmaps(tex, pixels, compress, "bc")
|
||||
|
||||
let info = KtxInfo(
|
||||
width: parts[0].width, height: parts[0].height, depth: tex.format_depth.int32,
|
||||
|
@ -256,7 +264,7 @@ when defined(myouUseAstcEncoder):
|
|||
assert err == ASTCENC_SUCCESS, "ASTC encoding error: " & $err
|
||||
return (data, row_len)
|
||||
|
||||
let (parts, data_refs) = make_mipmaps(tex, pixels, compress)
|
||||
let (parts, data_refs) = make_mipmaps(tex, pixels, compress, "astc")
|
||||
|
||||
let blk = (blk_size.first, blk_size.second)
|
||||
let info = KtxInfo(
|
||||
|
@ -274,12 +282,12 @@ func `%`(p: pointer): JsonNode = %0
|
|||
proc loadOptimized*(tex: Texture, slices: seq[SliceMem[byte]],
|
||||
callback_uncompressed: CallbackUncompressed = nil,
|
||||
callback_compressed: CallbackCompressed = nil,
|
||||
flip = true, min_channels = 0) {.gcsafe.} =
|
||||
flip = true, min_channels = myouMinTextureChannels) {.gcsafe.} =
|
||||
|
||||
let settings = tex.engine.cache_settings
|
||||
var min_channels = min_channels
|
||||
var will_compress = settings.compress_textures
|
||||
var will_load_uncompressed_first = false
|
||||
var will_load_uncompressed_first = myouLoadUncompressedTextures
|
||||
var will_encode_all = settings.compress_all_formats
|
||||
|
||||
will_compress = will_compress and
|
||||
|
@ -302,16 +310,28 @@ proc loadOptimized*(tex: Texture, slices: seq[SliceMem[byte]],
|
|||
if settings.use_cache and callback_compressed != nil:
|
||||
let cache_file = settings.cache_dir & "/" & cache_file_name
|
||||
# TODO: allow networked requests
|
||||
if fileExists cache_file:
|
||||
if myouAllCacheFilesExist or fileExists cache_file:
|
||||
try:
|
||||
let slices = readFile(cache_file).decompress.toSliceMem.to(byte).deserialize
|
||||
when defined(myouUseAndroidAssets):
|
||||
var asset = myouAndroidAPKFilePointerLength(cache_file.split("/", 1)[1])
|
||||
var f = newString(asset.len)
|
||||
copyMem(f.cstring.pointer, asset.p, asset.len)
|
||||
let slices = f.decompress.toSliceMem.to(byte).deserialize
|
||||
elif defined(ios) and myouAllCacheFilesExist:
|
||||
let cache_file = myouGetBundledFilePath("assets/" & cache_file.split("/", 1)[1])
|
||||
let slices = readFile(cache_file).decompress.toSliceMem.to(byte).deserialize
|
||||
else:
|
||||
let slices = readFile(cache_file).decompress.toSliceMem.to(byte).deserialize
|
||||
var data = to[KtxInfoParts](slices[^1].toString)
|
||||
for i,p in data.parts.mpairs:
|
||||
p.data = slices[i].toPointer
|
||||
tex.callback_compressed(data, slices)
|
||||
return
|
||||
except:
|
||||
except Exception as e:
|
||||
echo e.getStackTrace()
|
||||
echo getCurrentExceptionMsg()
|
||||
# TODO: proper error handling and logging
|
||||
echo cache_file
|
||||
echo "ERROR: could not load cache file for " & tex.name
|
||||
|
||||
if not (native_bc or native_astc):
|
||||
|
@ -344,7 +364,7 @@ proc loadOptimized*(tex: Texture, slices: seq[SliceMem[byte]],
|
|||
|
||||
let channels = tex.format.channel_count
|
||||
|
||||
when defined(myouUseBC7Encoder):
|
||||
when defined(myouUseBC7Encoder) and not defined(myouForceAstc):
|
||||
if has_bptc_support or will_encode_all:
|
||||
let bc_format = if channels == 1:
|
||||
4.int8 # BC4
|
||||
|
@ -391,7 +411,7 @@ when compileOption("threads"):
|
|||
proc loadOptimizedThreaded*(tex: Texture, slices: seq[SliceMem[byte]],
|
||||
callback_uncompressed: CallbackUncompressed = nil,
|
||||
callback_compressed: CallbackCompressed = nil,
|
||||
flip = true, min_channels = 0) =
|
||||
flip = true, min_channels = myouMinTextureChannels) =
|
||||
|
||||
when not compileOption("threads"):
|
||||
loadOptimized(tex, slices, callback_uncompressed, callback_compressed, flip, min_channels)
|
||||
|
|
|
@ -97,8 +97,24 @@ const VERTEX_SHADER_LIBRARY = dedent """
|
|||
}
|
||||
"""
|
||||
|
||||
when defined(myouStoreGLSL):
|
||||
var override_vs_code: Table[string, string]
|
||||
var override_fs_code: Table[string, string]
|
||||
proc setOverrideVSCode*(original, modified: string) =
|
||||
if original == modified:
|
||||
override_vs_code.del original
|
||||
else:
|
||||
override_vs_code[original] = modified
|
||||
proc setOverrideFSCode*(original, modified: string) =
|
||||
if original == modified:
|
||||
override_fs_code.del original
|
||||
else:
|
||||
override_fs_code[original] = modified
|
||||
|
||||
|
||||
proc console_error(msg: string) =
|
||||
echo msg
|
||||
for l in msg.split '\n':
|
||||
echo l
|
||||
|
||||
func add_line_numbers(s: string, first: int=1):string =
|
||||
var lines = s.strip(false, true, {'\n'}).split("\n")
|
||||
|
@ -208,6 +224,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
@[
|
||||
&"precision {precision} float;",
|
||||
&"precision {precision} int;",
|
||||
&"precision {precision} sampler2DArray;",
|
||||
&"precision {precision} sampler2DArrayShadow;",
|
||||
]
|
||||
else:
|
||||
|
@ -234,6 +251,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
fragment_lines.insert(material.scene.get_lighting_code_defines.join("\n"), def_index)
|
||||
if material.fragment == "":
|
||||
fragment_lines.add "void main(){}"
|
||||
var modifier_ubos: seq[UBO]
|
||||
var vs: string
|
||||
if self.material.vertex != "":
|
||||
vs = self.material.vertex
|
||||
|
@ -253,12 +271,14 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
var modifiers_uniforms, modifiers_bodies, modifiers_post_bodies: seq[string]
|
||||
var required_extensions: Table[string, bool]
|
||||
for m in modifiers:
|
||||
let (uniform_lines, body_lines, post_transform_lines, extensions) = m.get_code(self.material.varyings)
|
||||
modifiers_uniforms &= uniform_lines
|
||||
modifiers_bodies &= body_lines
|
||||
modifiers_post_bodies &= post_transform_lines
|
||||
for e in extensions:
|
||||
let code_lines = m.get_code(self.material.varyings)
|
||||
modifiers_uniforms &= code_lines.uniform_lines
|
||||
modifiers_bodies &= code_lines.body_lines
|
||||
modifiers_post_bodies &= code_lines.post_transform_lines
|
||||
for e in code_lines.extensions:
|
||||
required_extensions[e] = true
|
||||
if m.ubo != nil:
|
||||
modifier_ubos.add m.ubo
|
||||
var extension_lines: seq[string]
|
||||
for e in keys(required_extensions):
|
||||
extension_lines.add(&"#extension {e} : require")
|
||||
|
@ -386,8 +406,9 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
).join("\n ")
|
||||
vs = (version_line & extension_lines & vs_head & varyings_uniform_decl &
|
||||
attribute_lines & vertex_shader_library & modifiers_uniforms & varyings_decl & vs_body).join("\n")
|
||||
when not defined(release):
|
||||
when defined(myouStoreGLSL):
|
||||
self.vs_code = vs
|
||||
vs = override_vs_code.getOrDefault(vs, vs)
|
||||
let vertex_shader = glCreateShader(GL_VERTEX_SHADER)
|
||||
defer: glDeleteShader(vertex_shader)
|
||||
# TODO: make a pointer array from the unjoined strings, interleaved with "\n"
|
||||
|
@ -397,13 +418,14 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
glCompileShader(vertex_shader)
|
||||
var fragment_shader: GLuint = 0
|
||||
defer: glDeleteShader(fragment_shader)
|
||||
template fragment:string = fragment_lines.join("\n")
|
||||
var fragment = fragment_lines.join("\n")
|
||||
if has_fragment_shader:
|
||||
fragment_shader = glCreateShader(GL_FRAGMENT_SHADER)
|
||||
when not defined(release):
|
||||
when defined(myouStoreGLSL):
|
||||
self.fs_code = fragment
|
||||
var fragment2 = fragment.cstring
|
||||
glShaderSource(fragment_shader, 1, cast[cstringArray](addr fragment2), nil)
|
||||
fragment = override_fs_code.getOrDefault(fragment, fragment)
|
||||
var fragmentc = fragment.cstring
|
||||
glShaderSource(fragment_shader, 1, cast[cstringArray](addr fragmentc), nil)
|
||||
glCompileShader(fragment_shader)
|
||||
let prog = glCreateProgram()
|
||||
defer:
|
||||
|
@ -459,15 +481,17 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
return
|
||||
glGetProgramiv(prog, GL_LINK_STATUS, addr success)
|
||||
if success == 0:
|
||||
console_error("================")
|
||||
let error_msg = dedent &"""Error linking shader of material {material.name}
|
||||
{material.varyings}
|
||||
{get_program_info_log(prog)}"""
|
||||
console_error("VS =============")
|
||||
console_error(vs)
|
||||
# console_error 'FS ============='
|
||||
# console_error("VS =============")
|
||||
# console_error(vs)
|
||||
# console_error "FS ============="
|
||||
# console_error fragment
|
||||
console_error("================")
|
||||
# console_error("================")
|
||||
console_error error_msg
|
||||
console_error("================")
|
||||
return
|
||||
|
||||
self.camera_render_ubo_index = glGetUniformBlockIndex(prog, "CameraRenderUniform")
|
||||
|
@ -480,7 +504,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
|
|||
|
||||
var ubo_names = newSeqOfCap[string](material.ubos.len)
|
||||
self.ubos.setLen 0
|
||||
for ubo in material.ubos:
|
||||
for ubo in material.ubos & modifier_ubos:
|
||||
let idx = ubo.get_index prog
|
||||
if idx == GL_INVALID_INDEX:
|
||||
if ubo.byte_storage.len != 0:
|
||||
|
|
|
@ -261,6 +261,10 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren
|
|||
self.set_flip_normals mesh.flip
|
||||
let data {.cursor.} = amesh.data
|
||||
|
||||
for vmod in mesh.vertex_modifiers:
|
||||
if vmod.update.nonNil:
|
||||
vmod.update(mesh)
|
||||
|
||||
# unbindAllTextures()
|
||||
|
||||
# Main routine for each submesh
|
||||
|
@ -277,9 +281,11 @@ proc draw_mesh*(self: RenderManager, mesh: Mesh, mesh2world: Mat4, cam_data: Ren
|
|||
continue
|
||||
|
||||
var mat = if material_override == nil:
|
||||
amesh.materials.get_or_default(submesh_idx, self.no_material)
|
||||
amesh.materials.get_or_default(submesh_idx, nil)
|
||||
else:
|
||||
material_override
|
||||
if mat.isNil:
|
||||
mat = self.no_material
|
||||
|
||||
let shader {.cursor.} = mat.get_shader(mesh)
|
||||
let program = shader.use()
|
||||
|
|
|
@ -59,11 +59,14 @@ proc newTexture*(engine: MyouEngine, name: string, width, height: int, depth: in
|
|||
pixels: ArrRef[float32] = nil): Texture
|
||||
proc generateMipmap*(self: Texture)
|
||||
func to_sRGB*(format: TextureFormat): TextureFormat
|
||||
proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte], is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1, flip=true): Texture
|
||||
proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte],
|
||||
is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1,
|
||||
flip = true, use_compression = true): Texture
|
||||
proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: bool,
|
||||
filter: TextureFilter = Trilinear,
|
||||
tex_type: TextureType = Tex2D,
|
||||
flip = true,
|
||||
use_compression = true,
|
||||
): Texture
|
||||
proc setExtrapolation*(self: Texture, ext: TextureExtrapolation)
|
||||
proc getTexturePixels*(self: Texture): TexturePixels
|
||||
|
@ -440,7 +443,9 @@ func to_sRGB*(format: TextureFormat): TextureFormat =
|
|||
of RGB_u8: SRGB_u8
|
||||
else: raise newException(ValueError, "There's no sRGB version of " & $format)
|
||||
|
||||
proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte], is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1, flip=true): Texture =
|
||||
proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte],
|
||||
is_sRGB: bool, filter: TextureFilter = Trilinear, depth=1,
|
||||
flip = true, use_compression = true): Texture =
|
||||
var (width, height, format) = getDimensionsFormat(data.data, data.byte_len)
|
||||
if is_sRGB:
|
||||
if format in [RGB_u8, RGBA_u8]:
|
||||
|
@ -451,13 +456,17 @@ proc newTexture*(engine: MyouEngine, name: string, data: SliceMem[byte], is_sRGB
|
|||
let self = engine.newTexture(name, width, height, depth, format, filter=filter)
|
||||
self.is_sRGB = is_sRGB
|
||||
engine.renderer.enqueue proc() =
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData)
|
||||
if use_compression:
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData)
|
||||
else:
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, nil)
|
||||
return self
|
||||
|
||||
proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: bool,
|
||||
filter: TextureFilter = Trilinear,
|
||||
tex_type: TextureType = Tex2D,
|
||||
flip = true,
|
||||
use_compression = true,
|
||||
): Texture =
|
||||
|
||||
# TODO: Api that stores the LoadableResource so it can be re-loaded later
|
||||
|
@ -506,7 +515,10 @@ proc newTexture*(engine: MyouEngine, name: string, file_name: string, is_sRGB: b
|
|||
engine.renderer.enqueue proc()=
|
||||
try:
|
||||
self.ensure_storage()
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData)
|
||||
if use_compression:
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, loadCompressedData)
|
||||
else:
|
||||
self.loadOptimizedThreaded(@[data], loadFromPixels, nil)
|
||||
except:
|
||||
# TODO: use logging
|
||||
echo getCurrentExceptionMsg()
|
||||
|
|
|
@ -190,9 +190,10 @@ template is_valid*(block_index: GLuint): bool =
|
|||
block_index != GL_INVALID_INDEX
|
||||
|
||||
proc destroy*(self: UBO) =
|
||||
self.unbind()
|
||||
self.byte_storage = nil
|
||||
self.buffer = 0.GPUBuffer
|
||||
if self != nil:
|
||||
self.unbind()
|
||||
self.byte_storage = nil
|
||||
self.buffer = 0.GPUBuffer
|
||||
|
||||
# TODO: make a function/template/macro that generates GLSL code to declare the UBO from an object
|
||||
|
||||
|
|
|
@ -94,6 +94,7 @@ method close*(self: BlendLoader) =
|
|||
self.resource = nil
|
||||
self.blend_file = nil
|
||||
self.cached_materials.clear()
|
||||
self.textures.clear()
|
||||
|
||||
method openAssetFile*(self: BlendLoader, path: string) =
|
||||
if self.resource != nil:
|
||||
|
@ -204,17 +205,21 @@ method loadTextureImpl*(self: BlendLoader, name: string, img: FNode): Texture =
|
|||
return self.engine.newTexture(name,1,1,1,RGBA_f16,pixels=c16.to float32)
|
||||
# of 5: # UDIM sequence
|
||||
else:
|
||||
assert false, "Image source not supported yet: " & $source
|
||||
assert false, &"Image source not supported yet: {source}, image '{name}'"
|
||||
|
||||
method loadTexture*(self: BlendLoader, name: string): Texture =
|
||||
# sources:
|
||||
# 1 image
|
||||
# 4 generated
|
||||
# 5 render
|
||||
if name in self.textures:
|
||||
return self.textures[name]
|
||||
for img in self.blend_file.named_blocks["IM"]:
|
||||
let nm = img.id.name.str.strip2
|
||||
if name == nm:
|
||||
return self.loadTextureImpl(name, img)
|
||||
let tex = self.loadTextureImpl(name, img)
|
||||
self.textures[name] = tex
|
||||
return tex
|
||||
raise newException(KeyError, "Can't find image " & name)
|
||||
|
||||
proc makeMaterialAndTextures(self: BlendLoader;
|
||||
|
@ -288,10 +293,70 @@ proc idPropertiesToJsonTable(prop: FNode): Table[string, JsonNode] =
|
|||
else: discard
|
||||
prop = prop.next
|
||||
|
||||
proc loadFCurveImpl(self: BlendLoader, fcurve: FNode): (string, AnimationChannel) =
|
||||
let path = fcurve.rna_path.str
|
||||
let chan = new AnimationChannel
|
||||
let path_parts = path.rsplit('.',1)
|
||||
if path_parts.len == 1:
|
||||
chan.channel_type = ChObject
|
||||
else:
|
||||
if path.startswith "key_blocks[":
|
||||
chan.channel_type = ChShape
|
||||
chan.name = path.split('"')[1]
|
||||
elif path.startswith "pose.bones[":
|
||||
return # TODO
|
||||
else:
|
||||
echo "Warning: unknown channel path ", path
|
||||
return
|
||||
chan.property = case path_parts[^1]:
|
||||
of "value": PropValue
|
||||
of "location": PropPosition
|
||||
of "rotation_euler": PropRotationEuler
|
||||
of "rotation_quaternion": PropRotationQuaternion
|
||||
of "scale": PropScale
|
||||
else:
|
||||
echo "Warning: unknown property ", path
|
||||
return
|
||||
chan.index = fcurve.array_index.i32[0]
|
||||
if chan.property == PropRotationQuaternion:
|
||||
chan.index = (chan.index - 1) and 3
|
||||
let count = fcurve.totvert.i32[0]
|
||||
for i,floats in fcurve.bezt[0 ..< count].vec:
|
||||
let fs = floats.f32
|
||||
chan.points.add BezierPoint(
|
||||
# left_handle: vec2(fs[0], fs[1]),
|
||||
co: vec2(fs[3], fs[4]),
|
||||
# right_handle: vec2(fs[6], fs[7]),
|
||||
)
|
||||
if chan.property != PropValue:
|
||||
return (&"{path}[{chan.index}]", chan)
|
||||
else:
|
||||
return (path, chan)
|
||||
|
||||
proc loadActionImpl(self: BlendLoader, acn: FNode): Action =
|
||||
new(result)
|
||||
let flags = acn.flag.i32[0]
|
||||
result.manual_range = (flags and 4096).bool
|
||||
result.frame_start = acn.frame_start.f32[0]
|
||||
result.frame_end = acn.frame_end.f32[0]
|
||||
var curve = acn.curves.first
|
||||
while curve.valid:
|
||||
let (path, ch) = self.loadFCurveImpl(curve)
|
||||
if ch != nil:
|
||||
result.channels[path] = ch
|
||||
curve = curve.next
|
||||
|
||||
proc loadAction*(self: BlendLoader, name: string): Action =
|
||||
for n in self.blend_file.named_blocks["AC"]:
|
||||
if n.id.name.str.strip2 == name:
|
||||
return self.loadActionImpl(n)
|
||||
raise KeyError.newException &"Could not find action '{name}'"
|
||||
|
||||
proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, string) =
|
||||
let name = obn.id.name.str.strip2
|
||||
let data = obn.data
|
||||
let ob = case obn["type"].i16[0]:
|
||||
var shape_key_adt: FNode
|
||||
of BMesh.int16:
|
||||
var ob = self.engine.new_mesh(name=name)
|
||||
let mat_count = obn.totcol.i32[0]
|
||||
|
@ -301,7 +366,7 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
|||
ob.passes.setLen mat_count
|
||||
var tangents: seq[string]
|
||||
for i in 0 ..< mat_count:
|
||||
assert i < 16, "More than 16 materials not supported yet"
|
||||
assert i < 16, "More than 16 materials not supported yet" # TODO
|
||||
let is_oblink = matbits[i].testBit(0)
|
||||
let mat = if is_oblink:
|
||||
obn.mat[i]
|
||||
|
@ -313,6 +378,10 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
|||
continue
|
||||
|
||||
var backface_culling = (mat.blend_flag.i8[0] and 4'i8) != 0
|
||||
let blend_method = mat.blend_method.i8[0]
|
||||
let alpha_blend = blend_method == 5
|
||||
if alpha_blend:
|
||||
ob.passes[i] = 1
|
||||
|
||||
# this gives the "sons" error
|
||||
# dump (mat.id.name.cstr.strip2, backface_culling)
|
||||
|
@ -368,6 +437,8 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
|||
if v.vtype == Tangent and v.attname notin tangents:
|
||||
tangents.add v.attname
|
||||
|
||||
if data.key.valid:
|
||||
shape_key_adt = data.key.adt
|
||||
# TODO: defer this by storing the name of the mesh as hash
|
||||
# (maybe the address too)
|
||||
|
||||
|
@ -505,6 +576,20 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
|
|||
|
||||
ob.object_color = obn.col.f32.vec4
|
||||
|
||||
var animation_datas: seq[FNode]
|
||||
for adt in [obn.adt, shape_key_adt]:
|
||||
if adt.valid:
|
||||
animation_datas.add adt
|
||||
# TODO: make animation strips. we'll just merge the actions for now.
|
||||
for adt in animation_datas:
|
||||
if adt.action.valid:
|
||||
let ac = self.loadActionImpl(adt.action)
|
||||
if ob.action == nil:
|
||||
ob.action = ac
|
||||
else:
|
||||
for k,v in ac.channels:
|
||||
ob.action.channels[k] = v
|
||||
|
||||
let prop = obn.id.properties
|
||||
if prop.valid:
|
||||
ob.properties = idPropertiesToJsonTable(prop.data.group.first)
|
||||
|
@ -628,7 +713,7 @@ method loadScene*(self: BlendLoader, name: string="", scene: Scene=nil, callback
|
|||
callback(getCurrentExceptionMsg(), nil)
|
||||
return
|
||||
callback("", scene)
|
||||
if scene.name notin self.engine.new_scenes:
|
||||
if self.engine.new_del_scenes.getOrDefault(scene.name).isNil:
|
||||
# it was deleted
|
||||
return
|
||||
# TODO: when loading is async, move this stuff after loading has
|
||||
|
|
|
@ -348,6 +348,7 @@ proc cstr*(n: FNode): cstring =
|
|||
template str*(n: FNode): string = $(n.cstr)
|
||||
|
||||
template valid*(n: FNode): bool = n.p != nil
|
||||
template nonNil*(n: FNode): bool = n.p != nil
|
||||
template isNil*(n: FNode): bool = n.p == nil
|
||||
|
||||
proc strip2*(s: string): string {.inline.} =
|
||||
|
|
|
@ -44,6 +44,7 @@ import ../attributes
|
|||
import ./blend_format
|
||||
import ../util
|
||||
import ../objects/mesh
|
||||
import ../modifiers/shape_keys
|
||||
|
||||
template vec2(p: ptr array[16, float32]): Vec2 = cast[ptr Vec2](p)[]
|
||||
template vec3(p: ptr array[16, float32]): Vec3 = cast[ptr Vec3](p)[]
|
||||
|
@ -64,6 +65,20 @@ type AttrData = object
|
|||
count: int8
|
||||
buffer: ArrRef[int8]
|
||||
|
||||
|
||||
proc pack_normal(v: Vec3): float32 =
|
||||
let x = clamp(int32(v.x * 127), -127, 127)
|
||||
let y = clamp(int32((v.y + 1.0) * 127.5), 0, 255)
|
||||
let z = clamp(int32((v.z + 1.0) * 127.5), 0, 255)
|
||||
let i = 0x3f800000'i32 or
|
||||
((x and 0x80) shl 24) or
|
||||
((abs(x)) shl 16) or
|
||||
((y) shl 8) or
|
||||
(z)
|
||||
let f = cast[float32](i)
|
||||
return f - (if f >= 0.0: 1.0 else: -1.0)
|
||||
|
||||
|
||||
proc equals[T](a: ArrRef[T], s:seq[T], offset: int): bool =
|
||||
for i, v in a:
|
||||
if a[i] != s[offset+i]: return false
|
||||
|
@ -165,14 +180,14 @@ proc interleave_attributes(attributes: someArr[AttrData], indices: someArr[int32
|
|||
|
||||
|
||||
|
||||
proc calculate_normals(vertices: ArrRef[Vec3], indices, loop_sizes: ArrRef[int32]): seq[Vec3] =
|
||||
proc calculate_normals(vertices: ArrRef[Vec3] | ArrRef[Vec4], indices, loop_sizes: ArrRef[int32]): seq[Vec3] =
|
||||
result.setLen vertices.len
|
||||
var pos = 0
|
||||
var first, prev = vec3()
|
||||
for ls in loop_sizes:
|
||||
var n_sum = vec3()
|
||||
for i in 0 ..< ls:
|
||||
var v = vertices[indices[i+pos]]
|
||||
var v = vertices[indices[i+pos]].xyz
|
||||
case i:
|
||||
of 0:
|
||||
first = v
|
||||
|
@ -196,11 +211,13 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
|
|||
var vertices = newArrRef[Vec3](mesh.totvert.i32[0])
|
||||
var kb = mesh.key.`block`.first
|
||||
if kb.isNil:
|
||||
# TODO: delay this until after position attribute has been read?
|
||||
if mesh.mvert.valid:
|
||||
for i,co in mesh.mvert[0 ..< vertices.len].co:
|
||||
vertices[i] = co.f32.vec3
|
||||
else:
|
||||
# Use basis as source of "original" vertices
|
||||
# TODO: delay this until after position attribute has been read?
|
||||
let data = kb.data.get_array(vertices.len, Vec3)
|
||||
for i in 0 ..< vertices.len:
|
||||
vertices[i] = data[i]
|
||||
|
@ -303,17 +320,18 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
|
|||
of ".corner_edge":
|
||||
discard
|
||||
else:
|
||||
echo "Unknown int32 loop attribute, name ",layer.name.str
|
||||
echo "Unknown int32 loop attribute, name ", repr(layer.name.str)
|
||||
of 50: # bool
|
||||
case layer.name.str.split(".")[1]:
|
||||
of "vs":
|
||||
# UV map of all zeros or all ones
|
||||
# TODO: use static value instead of ignoring
|
||||
discard
|
||||
else:
|
||||
echo "Unknown bool loop attribute, name ",layer.name.str
|
||||
discard
|
||||
# case layer.name.str.split(".")[1]:
|
||||
# of "vs", "es":
|
||||
# # UV map of all zeros or all ones
|
||||
# # TODO: use static value instead of ignoring
|
||||
# discard
|
||||
# else:
|
||||
# echo "Unknown bool loop attribute, name ", repr(layer.name.str)
|
||||
else:
|
||||
echo "Unknown loop attribute type ", layer["type"].i32[0],", name ",layer.name.str
|
||||
echo "Unknown loop attribute type ", layer["type"].i32[0],", name ", repr(layer.name.str)
|
||||
# dump data
|
||||
for layer in mesh.vdata.arr_len(layers, totlayer):
|
||||
# echo layer["type"].i32[0]," v ",layer.name.str
|
||||
|
@ -377,7 +395,7 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
|
|||
used_materials[m] = true
|
||||
materials[i] = m.int16
|
||||
else:
|
||||
echo "Unknown int32 loop attribute, name ",layer.name.str
|
||||
echo "Unknown int32 loop attribute, name ", repr(layer.name.str)
|
||||
of 50: # bool
|
||||
case layer.name.str:
|
||||
of ".select_poly":
|
||||
|
@ -416,14 +434,45 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
|
|||
# for dv in mesh.dvert[0 ..< vertices.len]:
|
||||
# dump dv
|
||||
|
||||
# TODO: shape keys
|
||||
|
||||
# shape keys
|
||||
var shape_keys: OrderedTable[string, float32]
|
||||
var shape_key_arrays: seq[ArrRef[Vec4]]
|
||||
kb = mesh.key.`block`.first
|
||||
if kb.valid:
|
||||
kb = kb.next
|
||||
while kb.valid:
|
||||
let data = kb.data.get_array(vertices.len, Vec3)
|
||||
var out_array = newArrRef[Vec4](vertices.len)
|
||||
for i in 0 ..< vertices.len:
|
||||
out_array[i] = vec4(data[i], 0.0)
|
||||
shape_key_arrays.add out_array
|
||||
shape_keys[kb.name.str] = kb.curval.f32[0]
|
||||
kb = kb.next
|
||||
|
||||
# calculate normals
|
||||
let normals = calculate_normals(vertices, indices, loop_sizes)
|
||||
var normals_byte = newArrRef[i8vec4](normals.len)
|
||||
for i,n in normals:
|
||||
normals_byte[i] = [int8(n.x*127), int8(n.y*127), int8(n.z*127), 0]
|
||||
|
||||
# calculate normals of shape keys
|
||||
for shape in shape_key_arrays:
|
||||
let normals = calculate_normals(shape, indices, loop_sizes)
|
||||
for i,v in shape.mpairs:
|
||||
v.w = pack_normal(normals[i])
|
||||
|
||||
# make relative and add shape keys attributes
|
||||
for j,shape in shape_key_arrays:
|
||||
for i,v in shape.mpairs:
|
||||
v -= vec4(vertices[i], 0.0)
|
||||
|
||||
attributes.add AttrData(
|
||||
name: "shape" & $j, domain: DVertex,
|
||||
dtype: Float, count: 4,
|
||||
buffer: shape.to(int8),
|
||||
)
|
||||
|
||||
|
||||
# add empty tangents (to be calculated later)
|
||||
if required_tangents.len != 0:
|
||||
var tg_attributes: seq[AttrData]
|
||||
|
@ -542,6 +591,9 @@ proc loadMeshImpl*(self: BlendLoader, obn, mesh: FNode, ob: GameObject,
|
|||
final_ia.add newArrRef(ia_per_material[i])
|
||||
index_types.add UInt
|
||||
|
||||
ob.get_mesh.shape_keys = shape_keys
|
||||
if shape_keys.len != 0:
|
||||
ob.get_mesh.add_modifier(ob.engine.newShapeKeyModifier(shape_keys.len))
|
||||
ob.get_mesh.generate_tangents = required_tangents.len != 0
|
||||
ob.get_mesh.load_from_va_ia(final_va, final_ia, layout, index_types)
|
||||
# ob.get_mesh.debug_print_vertices(0,10)
|
||||
|
|
|
@ -642,7 +642,7 @@ node_functions = {
|
|||
let s = texture_uniform(img.name.str.strip2)
|
||||
var v = ins["Vector"].uv_socket
|
||||
# TODO: output float directly from single channel textures
|
||||
return mkOutput(&"$0 = texture({s}, {vec2(v)});", vec4tmp())
|
||||
return mkOutput(&"$0 = texture({s}, {vec2(v)});$1 = $0.a;", vec4tmp(), flttmp())
|
||||
,
|
||||
"ShaderNodeTexEnvironment": proc(ins: InputMapper): seq[Expr] {.closure.} =
|
||||
let img = ins.node.id
|
||||
|
@ -1015,7 +1015,7 @@ node_functions = {
|
|||
let metallic = ins["Metallic"].flt
|
||||
let roughness = ins["Roughness"].flt
|
||||
let ior = ins["IOR"].flt
|
||||
# let alpha = ins["Alpha"].flt
|
||||
let alpha = ins["Alpha"].flt
|
||||
let normal = ins["Normal"].normal_socket_to_world
|
||||
# let weight1 = ins["Weight"].flt
|
||||
# let subsurface_weight = ins["Subsurface Weight"].flt
|
||||
|
@ -1047,7 +1047,8 @@ node_functions = {
|
|||
&"{world_position()}, " &
|
||||
&"{metallic}, " &
|
||||
&"{roughness}, " &
|
||||
&"{ior} " &
|
||||
&"{ior}, " &
|
||||
&"{alpha} " &
|
||||
&") + vec4({emission_color} * {emission_strength}, 0.0);", vec4tmp())
|
||||
,
|
||||
"ShaderNodeRGBCurve": proc(ins: InputMapper): seq[Expr] {.closure.} =
|
||||
|
|
74
src/modifiers/shape_keys.nim
Normal file
74
src/modifiers/shape_keys.nim
Normal file
|
@ -0,0 +1,74 @@
|
|||
|
||||
import ../types
|
||||
import ../graphics/ubo
|
||||
import ../util
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/tables
|
||||
|
||||
proc newShapeKeyModifier*(engine: MyouEngine, count: int): VertexModifier =
|
||||
|
||||
let float_count = count.align4
|
||||
|
||||
result.get_code = proc(v: seq[Varying]): VertexModifierCodeLines =
|
||||
result.uniform_lines = @[
|
||||
"layout(std140) uniform ShapeKeys {",
|
||||
&" vec4 shape_values[{float_count div 4}];",
|
||||
"};",
|
||||
# 256/255 = 1.0039216 = 2.0078433 / 2.0
|
||||
# which helps us remap 0 to -1 and 255 to 1.
|
||||
"""
|
||||
vec3 unpack(float f){
|
||||
float x = f;
|
||||
float y = fract(abs(x)*128.0);
|
||||
float z = fract(y*256.0);
|
||||
y = y * 2.0078433 - 1.0;
|
||||
z = z * 2.0078433 - 1.0;
|
||||
return vec3(x,y,z);
|
||||
}
|
||||
""".dedent,
|
||||
]
|
||||
var body = @[
|
||||
# Equivalent to /= 127.0, and roughly to normalize byte normals
|
||||
"normal *= 0.007874;",
|
||||
"float relf = 0.0;",
|
||||
"vec3 co0 = co.xyz, orig_n = normal;",
|
||||
]
|
||||
for i in 0 ..< count:
|
||||
let i4 = i div 4
|
||||
body &= @[
|
||||
&"co += vec4(shape{i}.xyz, 0.0) * shape_values[{i4}][{i and 3}];",
|
||||
&"float shape{i}use = min(1.0, length(shape{i}.xyz) * 1000.0);",
|
||||
&"relf += abs(shape_values[{i4}][{i and 3}]) * shape{i}use;",
|
||||
]
|
||||
# Interpolating normals instead of re-calculating them is wrong
|
||||
# But it's fast, completely unnoticeable in most cases,
|
||||
# and better than just not changing them (as many engines do)
|
||||
# body &= "normal *= clamp(1.0 - relf, 0.0, 1.0);"
|
||||
body &= "vec3 snormal = normal * 0.001;"
|
||||
body &= "#define DEBUG1 0.001"
|
||||
body &= "#define DEBUG2 0.001"
|
||||
body &= "#define DEBUG3 0.001"
|
||||
for i in 0 ..< count:
|
||||
let i4 = i div 4
|
||||
# I substract 0.001 to avoid using noise near 0 which makes normals
|
||||
# change in places where a shape key is not affecting
|
||||
body &= &"""
|
||||
snormal += unpack(shape{i}.w) * max(0.0, shape_values[{i4}][{i and 3}] * shape{i}use)
|
||||
;"""
|
||||
body &= "normal = mix(normal, snormal, clamp(relf, 0.0, 1.0));"
|
||||
|
||||
result.body_lines = body
|
||||
|
||||
let ubo = engine.renderer.newUBO("ShapeKeys", float32, float_count)
|
||||
result.ubo = ubo
|
||||
|
||||
result.update = proc(m: Mesh) =
|
||||
var data = ubo.storage(float32)
|
||||
var i = 0
|
||||
for k,v in m.shape_keys:
|
||||
data[i] = v
|
||||
i.inc
|
||||
ubo.unbind()
|
||||
ubo.update()
|
||||
|
|
@ -173,11 +173,14 @@ proc myou_main_loop*(self: MyouEngine) =
|
|||
updateLoadableWorkerThreads()
|
||||
# TODO: make a table object that can be iterated while changing, e.g. with a
|
||||
# seq and a dirty flag to update the seq
|
||||
if self.new_scenes.len != 0:
|
||||
for name,scene in self.new_scenes.pairs:
|
||||
self.scenes[name] = scene
|
||||
self.new_scenes.clear()
|
||||
let time = getmonotime().ticks.float/1000000000
|
||||
if self.new_del_scenes.len != 0:
|
||||
for name,scene in self.new_del_scenes.pairs:
|
||||
if scene.nonNil:
|
||||
self.scenes[name] = scene
|
||||
else: # nil means that the scene is meant to be deleted
|
||||
self.scenes.del(name)
|
||||
self.new_del_scenes.clear()
|
||||
let time = getmonotime().ticks.float/1_000_000_000
|
||||
let delta_seconds = time - last_time
|
||||
for _,scene in self.scenes.pairs:
|
||||
if not scene.enabled:
|
||||
|
|
|
@ -51,12 +51,16 @@ import ./gameobject
|
|||
# import ../graphics/ubo
|
||||
|
||||
method is_camera*(self: GameObject): bool {.base.} =
|
||||
## Return whether a GameObject is a Camera
|
||||
return false
|
||||
method get_camera*(self: GameObject): Camera {.base.} =
|
||||
## Get the Camera object of a GameObject, or nil if it's not a camera
|
||||
return nil
|
||||
method is_camera*(self: Camera): bool =
|
||||
## Return whether a GameObject is a Camera
|
||||
return true
|
||||
method get_camera*(self: Camera): Camera =
|
||||
## Get the Camera object of a GameObject, or nil if it's not a camera
|
||||
return self
|
||||
|
||||
proc newCamera*(engine: MyouEngine, name: string="camera", scene: Scene = nil,
|
||||
|
@ -69,6 +73,8 @@ proc newCamera*(engine: MyouEngine, name: string="camera", scene: Scene = nil,
|
|||
sensor_fit: SensorFit = Auto,
|
||||
shift: Vec2 = vec2(),
|
||||
): Camera =
|
||||
## Create a new Camera object. If you supply `scene` it will be added to that
|
||||
## scene.
|
||||
var self = new Camera
|
||||
discard procCall(self.GameObject.initGameObject(engine, name))
|
||||
self.otype = TCamera
|
||||
|
@ -105,10 +111,16 @@ proc instance_physics*(self: Camera) =
|
|||
discard
|
||||
|
||||
proc get_ray_direction*(self: Camera, x, y: float32): Vec3 =
|
||||
## Converts a viewport coordinate to a vector direction in world space
|
||||
## relative to the camera. The upper left corner of the viewport has
|
||||
## coordinates (0,0), and the lower right corner (1,1).
|
||||
return self.get_world_rotation * (self.projection_matrix_inverse *
|
||||
vec3(x * 2 - 1, 1 - y * 2, 1))
|
||||
|
||||
proc get_ray_direction_local*(self: Camera, x, y: float32): Vec3 =
|
||||
## Converts a viewport coordinate to a vector direction in local space
|
||||
## relative to the camera. The upper left corner of the viewport has
|
||||
## coordinates (0,0), and the lower right corner (1,1).
|
||||
assert self.rotation_order == Quaternion
|
||||
return self.rotation * (self.projection_matrix_inverse *
|
||||
vec3(x * 2 - 1, 1 - y * 2, 1))
|
||||
|
@ -122,6 +134,11 @@ proc get_ray_direction_local*(self: Camera, x, y: float32): Vec3 =
|
|||
# procCall(self.GameObject.look_at(target, options))
|
||||
|
||||
proc is_vertical_fit*(self: Camera): bool =
|
||||
## Returns whether `field_of_view` represents the vertical field of view
|
||||
## according to `sensor_fit`. If it's false, `field_of_view` represents the
|
||||
## horizontal FoV.
|
||||
##
|
||||
## Only applicable when both `field_of_view` and `sensor_fit` are used.
|
||||
return case self.sensor_fit:
|
||||
of Auto: self.aspect_ratio <= 1
|
||||
of Horizontal: false
|
||||
|
@ -130,6 +147,12 @@ proc is_vertical_fit*(self: Camera): bool =
|
|||
of Contain: self.aspect_ratio > self.target_aspect_ratio
|
||||
|
||||
proc set_projection_matrix*(self: Camera, matrix: Mat4, adjust_aspect_ratio: bool = false) =
|
||||
## Sets a custom projection matrix to the camera, and optionally adjust the
|
||||
## aspect ratio if `adjust_aspect_ratio` is set to `true`. If you use this,
|
||||
## `field_of_view` won't be used.
|
||||
##
|
||||
## Note that the projection matrix will be reset if the viewport is resized,
|
||||
## so you may want to call this inside a resize event.
|
||||
self.projection_matrix = matrix
|
||||
if adjust_aspect_ratio:
|
||||
let m_ratio = matrix[0, 0] / matrix[1, 1]
|
||||
|
@ -144,10 +167,19 @@ proc set_projection_matrix*(self: Camera, matrix: Mat4, adjust_aspect_ratio: boo
|
|||
self.calculate_culling_planes()
|
||||
|
||||
proc update_projection*(self: Camera) =
|
||||
## Calculate projection matrices and culling planes from camera parameters.
|
||||
##
|
||||
## You may want to call this if you change any camera parameter.
|
||||
##
|
||||
## Called automatically on viewport creation and on resize events.
|
||||
self.calculate_projection()
|
||||
self.calculate_culling_planes()
|
||||
|
||||
proc calculate_projection*(self: Camera) =
|
||||
## Calculate projection matrices from camera parameters. Called automatically
|
||||
## on viewport creation and on resize events.
|
||||
##
|
||||
## If you change any camera parameters, use `update_projection`_ instead.
|
||||
let near_plane = self.near_plane
|
||||
let far_plane = self.far_plane
|
||||
var top, right, bottom, left: float32
|
||||
|
@ -208,6 +240,10 @@ proc calculate_projection*(self: Camera) =
|
|||
self.projection_matrix_inverse = inverse(self.projection_matrix)
|
||||
|
||||
proc calculate_culling_planes*(self: Camera) =
|
||||
## Calculate culling planes from the projection matrix. Called automatically
|
||||
## on viewport creation, on resize events, and with `set_projection_matrix`_.
|
||||
##
|
||||
## If you change any camera parameters, use `update_projection`_ instead.
|
||||
self.cull_planes = self.projection_matrix.get_culling_planes()
|
||||
|
||||
proc get_oblique_projection_matrix_into*(self: Camera, clip_plane: Vec4): Mat4 =
|
||||
|
|
|
@ -53,12 +53,16 @@ proc render_cubemap*(self: CubemapProbe, use_roughness_prefiltering = false, mip
|
|||
proc render_background_cubemap*(scene: Scene, use_roughness_prefiltering = false, mipmap_shader: Material = nil, world_to_cube_matrix: Mat4 = mat4(), upload_UBO = true)
|
||||
|
||||
method is_cubemap_probe*(self: GameObject): bool {.base.} =
|
||||
## Return whether a GameObject is a CubemapProbe
|
||||
return false
|
||||
method get_cubemap_probe*(self: GameObject): CubemapProbe {.base.} =
|
||||
## Get the CubemapProbe object of a GameObject, or nil if it's not a cubemap probe
|
||||
return nil
|
||||
method is_cubemap_probe*(self: CubemapProbe): bool =
|
||||
## Return whether a GameObject is a CubemapProbe
|
||||
return true
|
||||
method get_cubemap_probe*(self: CubemapProbe): CubemapProbe =
|
||||
## Get the CubemapProbe object of a GameObject, or nil if it's not a cubemap probe
|
||||
return self
|
||||
# End forward declarations and ob type methods
|
||||
|
||||
|
@ -82,6 +86,8 @@ proc newCubemapProbe*(engine: MyouEngine, name: string="camera", scene: Scene =
|
|||
parallax_type: ProbeParallaxType = NoParallax,
|
||||
parallax_distance: float32 = 0.0, # 0 means auto
|
||||
): CubemapProbe =
|
||||
## Create a new Cubemap Probe object. If you supply `scene` it will be added
|
||||
## to that scene.
|
||||
var self = new CubemapProbe
|
||||
discard procCall(self.GameObject.initGameObject(engine, name))
|
||||
self.ubo_index = -1
|
||||
|
@ -148,6 +154,7 @@ template get_roughness_prefilter(engine: MyouEngine): Material =
|
|||
roughness_prefilter
|
||||
|
||||
proc render_cubemap*(self: CubemapProbe, use_roughness_prefiltering = false, mipmap_shader: Material = nil) =
|
||||
## Updates the contents of the cubemap by rendering the scene on each side of the cubemap.
|
||||
if self.ubo_index == -1:
|
||||
self.scene.ensure_cubemaps()
|
||||
let cube2world = self.world_matrix * scale(vec3(self.influence_distance))
|
||||
|
@ -181,6 +188,7 @@ proc render_cubemap*(self: CubemapProbe, use_roughness_prefiltering = false, mip
|
|||
self.cubemap.disable()
|
||||
|
||||
proc render_background_cubemap*(scene: Scene, use_roughness_prefiltering = false, mipmap_shader: Material = nil, world_to_cube_matrix: Mat4 = mat4(), upload_UBO = true) =
|
||||
## Updates the contents of the background cubemap by rendering the world material on each side of the cubemap.
|
||||
if scene.background_cubemap == nil:
|
||||
scene.ensure_cubemaps()
|
||||
if not defined(release):
|
||||
|
@ -204,9 +212,9 @@ proc render_background_cubemap*(scene: Scene, use_roughness_prefiltering = false
|
|||
if upload_UBO:
|
||||
scene.cubemap_UBO.update()
|
||||
|
||||
proc generate_cubemap_mipmaps*(scene: Scene, cubemap: Framebuffer, use_roughness_prefiltering = false, mipmap_shader: Material = nil) =
|
||||
let mipmap_shader = if use_roughness_prefiltering and mipmap_shader == nil:
|
||||
scene.engine.get_roughness_prefilter()
|
||||
else:
|
||||
mipmap_shader
|
||||
cubemap.generate_mipmap(mipmap_shader)
|
||||
proc generate_cubemap_mipmaps_with_roughness_prefiltering*(scene: Scene, cubemap: Framebuffer) =
|
||||
## Generates cubemap.generate_mipmap but using the roughness prefilter. This
|
||||
## is called automatically when rendering cubemaps with
|
||||
## `use_roughness_prefiltering = true`. Use it only when you write data to
|
||||
## the cubemap manually.
|
||||
cubemap.generate_mipmap(scene.engine.get_roughness_prefilter())
|
||||
|
|
|
@ -144,26 +144,40 @@ proc initGameObject*(self: GameObject, engine: MyouEngine, name: string="", scen
|
|||
return self
|
||||
|
||||
proc newGameObject*(engine: MyouEngine, name: string="", scene: Scene=nil): GameObject =
|
||||
result = new GameObject
|
||||
## Create a new GameObject. If you supply `scene` it will be added to that
|
||||
## scene.
|
||||
new(result)
|
||||
return initGameObject(result, engine, name, scene)
|
||||
|
||||
proc show*(self: GameObject, recursive: static[bool] = true) =
|
||||
## Enable visibility of the object, its children, their children and so on.
|
||||
## If you set `recursive = false`, only the visibility of the object will be
|
||||
## changed.
|
||||
self.visible = true
|
||||
when recursive:
|
||||
for c in self.children:
|
||||
c.show(recursive)
|
||||
|
||||
proc hide*(self: GameObject, recursive: static[bool] = true) =
|
||||
## Disable visibility of the object, its children, their children and so on.
|
||||
## If you set `recursive = false`, only the visibility of the object will be
|
||||
## changed.
|
||||
self.visible = false
|
||||
when recursive:
|
||||
for c in self.children:
|
||||
c.hide(recursive)
|
||||
|
||||
proc set_visibility*(self: GameObject, visible: bool, recursive: static[bool] = true) =
|
||||
## Set the visibility of the object and its descendants to the value of the
|
||||
## argument `visible`. If you set `recursive = false`, only the visibility of
|
||||
## the object will be changed.
|
||||
if visible: self.show(recursive)
|
||||
else: self.hide(recursive)
|
||||
|
||||
proc update_matrices*(self: GameObject) =
|
||||
## Calculate the matrices of the object from its position, rotation, scale,
|
||||
## and parent matrix. It assumes the parent object and/or bone has its world
|
||||
## matrix already up to date.
|
||||
var q = if self.rotation_order == Quaternion:
|
||||
self.rotation
|
||||
else:
|
||||
|
@ -214,11 +228,14 @@ proc update_matrices*(self: GameObject) =
|
|||
return
|
||||
|
||||
proc update_matrices_recursive(self: GameObject) =
|
||||
## Calculate the matrices of the object from its position, rotation, scale,
|
||||
## and parent. It will update the parent matrices first if it has a parent.
|
||||
if self != nil:
|
||||
self.parent.update_matrices_recursive()
|
||||
self.update_matrices()
|
||||
|
||||
proc set_rotation_order*(self: GameObject, order: RotationOrder) =
|
||||
## Change the rotation mode and order of the object.
|
||||
if order == self.rotation_order:
|
||||
return
|
||||
var q = self.rotation
|
||||
|
@ -237,11 +254,14 @@ proc set_rotation_order*(self: GameObject, order: RotationOrder) =
|
|||
self.rotation_order = order
|
||||
|
||||
proc get_world_matrix*(self: GameObject): Mat4 =
|
||||
## Calculates and returns the world matrix.
|
||||
# TODO: use dirty flag
|
||||
self.update_matrices_recursive()
|
||||
return self.world_matrix
|
||||
|
||||
proc get_local_matrix*(self: GameObject): Mat4 =
|
||||
## Calculates and returns the transformation matrix in local space. If the
|
||||
## object has no parent, this is equivalent to the world matrix.
|
||||
var q = if self.rotation_order == Quaternion:
|
||||
self.rotation
|
||||
else:
|
||||
|
@ -271,16 +291,19 @@ proc get_local_matrix*(self: GameObject): Mat4 =
|
|||
)
|
||||
|
||||
proc get_world_position*(self: GameObject): Vec3 =
|
||||
## Calculates and returns the position in world space.
|
||||
if self.parent == nil:
|
||||
return self.position
|
||||
return self.get_world_matrix[3].xyz
|
||||
|
||||
proc get_world_rotation*(self: GameObject): Quat =
|
||||
## Calculates and returns the rotation in world space as a quaternion.
|
||||
let wm = self.get_world_matrix
|
||||
# TODO: Calculate rotation matrix more efficiently (dirty flag?)
|
||||
return wm.to_mat3_rotation.to_quat
|
||||
|
||||
proc get_world_position_rotation*(self: GameObject): (Vec3, Quat) =
|
||||
## Calculates and returns the position and rotation in world space.
|
||||
let wm = self.get_world_matrix
|
||||
let position: Vec3 = wm[3].xyz
|
||||
# TODO: Calculate rotation matrix more efficiently (dirty flag?)
|
||||
|
@ -289,6 +312,9 @@ proc get_world_position_rotation*(self: GameObject): (Vec3, Quat) =
|
|||
return (position, rotation)
|
||||
|
||||
proc translate*(self: GameObject, vector: Vec3, relative_object: GameObject=nil): GameObject {.discardable.} =
|
||||
## Translate (move) the object by adding the vector `vector` to its position,
|
||||
## either relative to the scene, or relative to the specified object in
|
||||
## `relative_object`
|
||||
var vector = vector
|
||||
if relative_object != nil:
|
||||
vector = relative_object.get_world_rotation() * vector
|
||||
|
@ -299,12 +325,18 @@ proc translate*(self: GameObject, vector: Vec3, relative_object: GameObject=nil)
|
|||
return self
|
||||
|
||||
proc translate_x*(self: GameObject, x: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.} =
|
||||
## Translate (move) the object along the X axis of the scene, or the X axis
|
||||
## of the specified object in `relative_object`
|
||||
return self.translate(vec3(x, 0, 0), relative_object)
|
||||
|
||||
proc translate_y*(self: GameObject, y: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.} =
|
||||
## Translate (move) the object along the Y axis of the scene, or the Y axis
|
||||
## of the specified object in `relative_object`
|
||||
return self.translate(vec3(0, y, 0), relative_object)
|
||||
|
||||
proc translate_z*(self: GameObject, z: SomeFloat, relative_object: GameObject=nil): GameObject {.discardable.} =
|
||||
## Translate (move) the object along the Z axis of the scene, or the Z axis
|
||||
## of the specified object in `relative_object`
|
||||
return self.translate(vec3(0, 0, z), relative_object)
|
||||
|
||||
# proc rotate_euler*(self: GameObject, vector: Vec3, relative_object: GameObject=nil): GameObject =
|
||||
|
@ -317,6 +349,10 @@ proc translate_z*(self: GameObject, z: SomeFloat, relative_object: GameObject=ni
|
|||
# self.rotate_quat(q, relative_object)
|
||||
|
||||
proc rotate_quat*(self: GameObject, q: Quat, relative_object: GameObject=nil): GameObject {.discardable.} =
|
||||
# Rotate the object by applying (multiplying) the quaternion `q`, either
|
||||
# relative to the scene, or relative to the specified object in
|
||||
# `relative_object`
|
||||
|
||||
# TODO: optimize (dirty flag?)
|
||||
var rel = quat()
|
||||
var inv_rel = quat()
|
||||
|
@ -338,28 +374,42 @@ proc rotate_quat*(self: GameObject, q: Quat, relative_object: GameObject=nil): G
|
|||
return self
|
||||
|
||||
proc rotate_x*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
|
||||
## Rotate the object around the X axis of the scene, or the X axis of the
|
||||
## specified object in `relative_object`. The angle is in radians.
|
||||
self.rotate_quat(rotateX(angle.float32).to_mat3.to_quat, relative_object)
|
||||
|
||||
proc rotate_y*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
|
||||
## Rotate the object around the Y axis of the scene, or the Y axis of the
|
||||
## specified object in `relative_object`. The angle is in radians.
|
||||
self.rotate_quat(rotateY(angle.float32).to_mat3.to_quat, relative_object)
|
||||
|
||||
proc rotate_z*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
|
||||
## Rotate the object around the Z axis of the scene, or the Z axis of the
|
||||
## specified object in `relative_object`. The angle is in radians.
|
||||
self.rotate_quat(rotateZ(angle.float32).to_mat3.to_quat, relative_object)
|
||||
|
||||
proc rotate_x_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
|
||||
## Rotate the object around the X axis of the scene, or the X axis of the
|
||||
## specified object in `relative_object`. The angle is in degrees.
|
||||
self.rotate_quat(rotateX(angle.float32.to_radians).to_mat3.to_quat, relative_object)
|
||||
|
||||
proc rotate_y_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
|
||||
## Rotate the object around the Y axis of the scene, or the Y axis of the
|
||||
## specified object in `relative_object`. The angle is in degrees.
|
||||
self.rotate_quat(rotateY(angle.float32.to_radians).to_mat3.to_quat, relative_object)
|
||||
|
||||
proc rotate_z_deg*(self: GameObject, angle: SomeFloat, relative_object: GameObject=nil) =
|
||||
## Rotate the object around the Z axis of the scene, or the Z axis of the
|
||||
## specified object in `relative_object`. The angle is in degrees.
|
||||
self.rotate_quat(rotateZ(angle.float32.to_radians).to_mat3.to_quat, relative_object)
|
||||
|
||||
proc set_world_position_rotation*(
|
||||
self: GameObject,
|
||||
position: Vec3,
|
||||
rotation: Quat) =
|
||||
|
||||
## Changes the position and the rotation of the object to match the supplied
|
||||
## position and rotation in world space.
|
||||
|
||||
if self.parent != nil:
|
||||
# TODO: optimize and use less memory?
|
||||
# TODO: does it work well with parents?
|
||||
|
@ -372,9 +422,13 @@ proc set_world_position_rotation*(
|
|||
self.rotation_order = Quaternion
|
||||
|
||||
proc set_world_position*(self: GameObject, position: Vec3) =
|
||||
## Changes the position of the object to match the supplied position in world
|
||||
## space.
|
||||
self.set_world_position_rotation(position, self.get_world_rotation())
|
||||
|
||||
proc set_world_rotation*(self: GameObject, rotation: Quat) =
|
||||
## Changes the rotation of the object to match the supplied rotation in world
|
||||
## space.
|
||||
self.set_world_position_rotation(self.get_world_position(), rotation)
|
||||
|
||||
type LookAtAxis* = enum
|
||||
|
@ -524,12 +578,13 @@ proc destroy*(self: GameObject, recursive: bool = true) =
|
|||
var child = child
|
||||
child.destroy(true)
|
||||
self.engine.objects.del(self.name)
|
||||
self.object_render_ubo.unbind()
|
||||
self.object_render_ubo.destroy()
|
||||
self.object_ubo.unbind()
|
||||
self.object_ubo.destroy()
|
||||
|
||||
proc convert_bone_child_to_bone_parent*(self: GameObject) =
|
||||
## Turns an object that is parented to a bone, into the parent of the same
|
||||
## bone. It disconnects the bone from its former parent, if any. Used
|
||||
## internally to create ragdolls.
|
||||
assert self.parent == nil or self.parent.is_armature, "An armature parent is required"
|
||||
var parent = self.parent.get_armature
|
||||
if not (parent != nil and (self.parent_bone_index) >= 0):
|
||||
|
@ -635,6 +690,7 @@ proc set_world_size*(self: GameObject, size: SomeFloat) =
|
|||
return
|
||||
|
||||
proc set_name*(self: GameObject, name: string) =
|
||||
## Rename the object and update the tables that use it.
|
||||
assert self.scene != nil, "Object has no scene"
|
||||
self.engine.objects.del(self.name)
|
||||
self.scene.objects.del(self.name)
|
||||
|
|
|
@ -55,12 +55,16 @@ proc newLight*(engine: MyouEngine, name: string="",
|
|||
proc instance_physics*(this: Light)
|
||||
|
||||
method is_light*(self: GameObject): bool {.base.} =
|
||||
## Return whether a GameObject is a Light
|
||||
return false
|
||||
method get_light*(self: GameObject): Light {.base.} =
|
||||
## Get the Light object of a GameObject, or nil if it's not a light
|
||||
return nil
|
||||
method is_light*(this: Light): bool =
|
||||
## Return whether a GameObject is a Light
|
||||
return true
|
||||
method get_light*(this: Light): Light =
|
||||
## Get the Light object of a GameObject, or nil if it's not a light
|
||||
return this
|
||||
# End forward declarations and ob type methods
|
||||
|
||||
|
@ -84,6 +88,8 @@ proc newLight*(engine: MyouEngine, name: string="",
|
|||
spot_blend: float32 = 0.15,
|
||||
use_shadow = false,
|
||||
): Light =
|
||||
## Create a new Light object. If you supply `scene` it will be added to that
|
||||
## scene.
|
||||
var this = new Light
|
||||
discard procCall(this.GameObject.initGameObject(engine, name))
|
||||
this.otype = TLight
|
||||
|
@ -108,15 +114,15 @@ proc configure_shadow*(self: Light,
|
|||
camera: Camera = nil,
|
||||
max_distance: float32 = 0.0,
|
||||
objects = self.scene.children) =
|
||||
# Configure a shadow for this light object, either by following a camera
|
||||
# passed as argument, or a static shadow for the specified objects.
|
||||
#
|
||||
# `max_distance` is only used when `camera` is supplied.
|
||||
#
|
||||
# `objects` is only used in static mode (when `camera` is nil). In this
|
||||
# mode, the shadow map will be tailored for the size of the bounding boxes
|
||||
# of the objects. It will exclude flat objects (planes) that are under every
|
||||
# other object because they can't project a shadow to themselves.
|
||||
## Configure a shadow for this light object, either by following a camera
|
||||
## passed as argument, or a static shadow for the specified objects.
|
||||
##
|
||||
## `max_distance` is only used when `camera` is supplied.
|
||||
##
|
||||
## `objects` is only used in static mode (when `camera` is nil). In this
|
||||
## mode, the shadow map will be tailored for the size of the bounding boxes
|
||||
## of the objects. It will exclude flat objects (planes) that are under every
|
||||
## other object because they can't project a shadow to themselves.
|
||||
for s in self.shadows:
|
||||
s.destroy()
|
||||
self.shadows.setLen 0
|
||||
|
@ -162,7 +168,6 @@ proc configure_shadow*(self: Light,
|
|||
break
|
||||
casts = not all_above
|
||||
if casts:
|
||||
echo "adding casting ", ob.name
|
||||
for v in box_corners(bb):
|
||||
casters.add ob.world_matrix * v
|
||||
|
||||
|
|
|
@ -49,7 +49,6 @@ proc load_from_va_ia*(self: Mesh,
|
|||
index_types: seq[DataType] = @[])
|
||||
proc generate_tangents*(self: MeshData, uv_layer_name, tangent_layer_name: string)
|
||||
proc ensure_capacity*(self: Mesh, extra_elements: int)
|
||||
proc write_vaos*(self: MeshData)
|
||||
# End forward declarations and ob type methods
|
||||
|
||||
|
||||
|
@ -72,20 +71,27 @@ export tables
|
|||
import ../platform/gl
|
||||
|
||||
method is_mesh*(self: GameObject): bool {.base.} =
|
||||
## Return whether a GameObject is a Mesh
|
||||
return false
|
||||
method get_mesh*(self: GameObject): Mesh {.base.} =
|
||||
## Get the Mesh object of a GameObject, or nil if it's not a mesh
|
||||
return nil
|
||||
method is_mesh*(self: Mesh): bool =
|
||||
## Return whether a GameObject is a Mesh
|
||||
return true
|
||||
method get_mesh*(self: Mesh): Mesh =
|
||||
## Get the Mesh object of a GameObject, or nil if it's not a mesh
|
||||
return self
|
||||
|
||||
proc newMeshData(engine: MyouEngine): MeshData =
|
||||
## Creates a new MeshData. It's recommended to use newMesh arguments instead.
|
||||
result = new MeshData
|
||||
result.draw_method = Triangles
|
||||
result.engine = engine
|
||||
|
||||
proc remove*(self: MeshData, ob: Mesh, delete_buffers: bool = true) =
|
||||
## Removes this MeshData from a mesh object, and deletes itself if it doesn't
|
||||
## have any more users.
|
||||
var idx = self.users.find(ob)
|
||||
while idx != -1:
|
||||
self.users.delete(idx)
|
||||
|
@ -94,7 +100,7 @@ proc remove*(self: MeshData, ob: Mesh, delete_buffers: bool = true) =
|
|||
self.engine.mesh_datas.del(self.hash)
|
||||
ob.data = nil
|
||||
|
||||
proc write_vaos*(self: MeshData) =
|
||||
proc write_vaos(self: MeshData) =
|
||||
for i,vao in self.vaos:
|
||||
if vao.int == 0:
|
||||
continue
|
||||
|
@ -209,6 +215,7 @@ proc gpu_buffers_upload*(self: MeshData) =
|
|||
# set loaded to true after that
|
||||
|
||||
proc gpu_buffers_delete*(self: MeshData) =
|
||||
## Removes GPU buffers. For internal use. Use GameObject.destroy() instead.
|
||||
glBindVertexArray(0)
|
||||
self.vaos = @[]
|
||||
self.tf_vaos = @[]
|
||||
|
@ -222,6 +229,8 @@ proc gpu_buffers_delete*(self: MeshData) =
|
|||
self.engine.mesh_datas.del(self.hash)
|
||||
|
||||
proc update_varray*(self: MeshData) =
|
||||
## Update vertex GPU buffers with the contents of vertex arrays. Call this
|
||||
## after updating vertex arrays.
|
||||
if self.vertex_buffers.len == 0:
|
||||
return
|
||||
for i,va in self.varrays:
|
||||
|
@ -229,6 +238,8 @@ proc update_varray*(self: MeshData) =
|
|||
glBufferSubData(GL_ARRAY_BUFFER, 0.GLintptr, cast[GLsizeiptr](va.bytelen), addr va[0])
|
||||
|
||||
proc update_iarray*(self: MeshData) =
|
||||
## Update index GPU buffers with the contents of vertex arrays. Call this
|
||||
## after updating index arrays.
|
||||
if self.index_buffers.len == 0:
|
||||
return
|
||||
for i,ia in self.iarrays:
|
||||
|
@ -241,7 +252,7 @@ proc clone*(self: MeshData): MeshData =
|
|||
d.users = @[]
|
||||
return d
|
||||
|
||||
proc initMesh*(self: Mesh, engine: MyouEngine, name: string, scene: Scene = nil,
|
||||
proc initMesh(self: Mesh, engine: MyouEngine, name: string, scene: Scene = nil,
|
||||
draw_method: MeshDrawMethod = Triangles,
|
||||
common_attributes: CommonMeshAttributes = {vertex, color},
|
||||
layout: AttributeList = @[],
|
||||
|
@ -267,26 +278,15 @@ proc initMesh*(self: Mesh, engine: MyouEngine, name: string, scene: Scene = nil,
|
|||
self.layout.add Attribute(name: "normal", dtype: Byte, count: 4)
|
||||
if uv in common_attributes:
|
||||
self.layout.add Attribute(name: "uv_0", dtype: Float, count: 2)
|
||||
# self.stride = if stride != 0: stride else: self.layout.stride
|
||||
let stride = self.layout.stride
|
||||
|
||||
if vertex_count != 0:
|
||||
if vertex_array.len != 0:
|
||||
var va = newArrRef(vertex_array)
|
||||
var ia = newArrRef(index_array)
|
||||
self.skip_upload = skip_upload
|
||||
self.load_from_va_ia(@[va], @[ia])
|
||||
elif vertex_count != 0:
|
||||
self.ensure_capacity(vertex_count)
|
||||
|
||||
# var va = newArrRef(vertex_array)
|
||||
# if vertex_count != 0:
|
||||
# va.set_len max(va.len, vertex_count * self.stride div 4)
|
||||
# if va.len != 0:
|
||||
# var ia = newArrRef(index_array)
|
||||
# self.skip_upload = skip_upload
|
||||
# self.load_from_va_ia(@[va], @[ia])
|
||||
# self.data.num_indices ?= @[0.int32]
|
||||
# if ?vertex_array:
|
||||
# if not ?ia:
|
||||
# self.data.num_indices[0] = vertex_count
|
||||
# assert((self.data.varray.bytelen div self.data.stride) >= vertex_count,
|
||||
# &"Array is {self.data.varray.bytelen} bytes long but expected at least {vertex_count * self.data.stride}")
|
||||
# else:
|
||||
# self.data.num_indices[0] = 0
|
||||
if scene != nil:
|
||||
scene.add_object(self, name=name)
|
||||
return self
|
||||
|
@ -302,14 +302,42 @@ proc newMesh*(engine: MyouEngine, name: string="mesh", scene: Scene = nil,
|
|||
index_array: seq[uint16] = @[],
|
||||
pass: int32 = 0,
|
||||
): Mesh =
|
||||
result = new Mesh
|
||||
## Create a new Mesh object. If you supply `scene` it will be added to that
|
||||
## scene.
|
||||
##
|
||||
## The most common draw methods are `Triangles` (the default), `Points`,
|
||||
## `Lines`, and `TriangleStrip` (for `add_polygonal_line()`).
|
||||
##
|
||||
## If you supply a layout, it will be used. Otherwise a layout will be
|
||||
## created from `common_attributes`, which by default is `{vertex, color}`.
|
||||
## The available common attributes are `{vertex, color, normal, uv}` and they
|
||||
## will be added in that order.
|
||||
##
|
||||
## You can give a `vertex_count` to allocate a mesh with capacity for that
|
||||
## amount of vertices, but you can always resize it later. Meant to be used
|
||||
## with `add_vertex()` and `add_polygonal_line`
|
||||
##
|
||||
## If you give a vertex array and an index array, they will be used directly
|
||||
## as GPU buffers. If you only supply the vertex array, indices will
|
||||
## implicitely be sequential.
|
||||
|
||||
# TODO: document pass and skip_upload
|
||||
new(result)
|
||||
result.otype = TMesh
|
||||
|
||||
return initMesh(result, engine, name, scene, draw_method, common_attributes, layout,
|
||||
skip_upload, vertex_count, vertex_array, index_array, pass)
|
||||
|
||||
proc add_vertex*(self: Mesh, x, y, z: float32, r, g, b, a: uint8): int {.discardable.} =
|
||||
## Tries to add a vertex to the mesh with the specified position and color.
|
||||
## It will fill an unused vertex if any. Returns the vertex index, or -1 if
|
||||
## the vertex could not be added.
|
||||
##
|
||||
## At the moment you need to call mesh.data.update_varray() after adding
|
||||
## vertices. It's most efficient when only doing it once.
|
||||
|
||||
# TODO: Check it actually has a color attribute!!
|
||||
# TODO: ensure it doesn't have a, index array?
|
||||
var varray = self.data.varrays[0]
|
||||
var varray_byte = varray.to(uint8)
|
||||
let stride = self.data.stride
|
||||
|
@ -331,18 +359,38 @@ proc add_vertex*(self: Mesh, x, y, z: float32, r, g, b, a: uint8): int {.discard
|
|||
return index+1
|
||||
|
||||
proc add_vertex*(self: Mesh, x, y, z, r, g, b, a: SomeFloat): int {.discardable.} =
|
||||
## Tries to add a vertex to the mesh with the specified position and color.
|
||||
## It will fill an unused vertex if any. Returns the vertex index, or -1 if
|
||||
## the vertex could not be added.
|
||||
##
|
||||
## At the moment you need to call mesh.data.update_varray() after adding
|
||||
## vertices. It's most efficient when only doing it once.
|
||||
return self.add_vertex(x, y, z, (r*255).uint8, (g*255).uint8, (b*255).uint8, (a*255).uint8)
|
||||
|
||||
proc add_vertex*(self: Mesh, position: Vec3, color: Vec4): int {.discardable.} =
|
||||
proc add_vertex*(self: Mesh, position: Vec3, color: Vec4 = vec4(1)): int {.discardable.} =
|
||||
## Tries to add a vertex to the mesh with the specified position and color.
|
||||
## It will fill an unused vertex if any. Returns the vertex index, or -1 if
|
||||
## the vertex could not be added.
|
||||
##
|
||||
## At the moment you need to call mesh.data.update_varray() after adding
|
||||
## vertices. It's most efficient when only doing it once.
|
||||
let (x,y,z) = position.to_tuple
|
||||
let (r,g,b,a) = color.to_tuple
|
||||
return self.add_vertex(x, y, z, r, g, b, a)
|
||||
|
||||
proc clear_vertices*(self: Mesh) =
|
||||
## Removes all vertices of the mesh by marking them as unused. You don't need
|
||||
## to call update_varray()
|
||||
self.data.num_indices[0] = 0
|
||||
|
||||
proc remove_vertex*(self: Mesh, index: int) =
|
||||
# TODO: This is only valid for points mode
|
||||
## Removes a vertices of the mesh by marking it as unused. At the moment it
|
||||
## only works in points mode.
|
||||
##
|
||||
## At the moment you need to call mesh.data.update_varray() after adding or
|
||||
## removing vertices. It's most efficient when only doing it once.
|
||||
|
||||
# TODO: Remove a whole line/triangle in line/triangle mode.
|
||||
if not (index >= 0):
|
||||
return # ignore -1
|
||||
assert self.data.num_indices[0] > index, "Invalid index"
|
||||
|
@ -356,6 +404,8 @@ proc remove_vertex*(self: Mesh, index: int) =
|
|||
self.data.num_indices[0] -= 1
|
||||
|
||||
proc ensure_capacity*(self: Mesh, extra_elements: int) =
|
||||
## Checks if there's enough space for the desired number of elements to be
|
||||
## added, and resizes the mesh if it's needed (by a factor of two).
|
||||
if self.data == nil:
|
||||
let stride = self.layout.stride
|
||||
assert stride != 0
|
||||
|
@ -395,6 +445,11 @@ proc load_from_va_ia*(self: Mesh,
|
|||
iarrays: seq[ArrRef[uint16]] | seq[ArrRef[int32]] = newSeq[ArrRef[uint16]](),
|
||||
layout: AttributeList = @[],
|
||||
index_types: seq[DataType] = @[]) =
|
||||
## Loads the mesh to the GPU from the given vertex and index arrays, layout
|
||||
## and index type. It's recommended to use newMesh arguments instead.
|
||||
##
|
||||
## This version takes multiple vertex and index arrays, which can have 16 or
|
||||
## 32 bit indices.
|
||||
|
||||
if self.data != nil:
|
||||
self.data.remove(self)
|
||||
|
@ -446,9 +501,16 @@ proc load_from_va_ia*(self: Mesh,
|
|||
varray: seq[float32],
|
||||
iarray: seq[uint16] = @[],
|
||||
layout: AttributeList = @[]) =
|
||||
## Loads the mesh to the GPU from the given vertex and index arrays and
|
||||
## layout. It's recommended to use newMesh arguments instead.
|
||||
##
|
||||
## This version takes a single vertex array and an optional index array, with
|
||||
## 16 bit indices.
|
||||
self.load_from_va_ia(@[newArrRef varray], @[newArrRef iarray], layout)
|
||||
|
||||
proc add_modifier*(self: Mesh, modifier: VertexModifier) =
|
||||
## Add a vertex modifier to the mesh, and changes the mesh data if necessary.
|
||||
## It's added to the end of the stack.
|
||||
self.vertex_modifiers.add(modifier)
|
||||
if modifier.prepare_mesh.nonNil and self.data.nonNil:
|
||||
echo &"applying modifiers of {self.name} after it was already loaded"
|
||||
|
@ -456,6 +518,8 @@ proc add_modifier*(self: Mesh, modifier: VertexModifier) =
|
|||
# self.update_signature()
|
||||
|
||||
proc insert_modifier*(self: Mesh, index: int, modifier: VertexModifier) =
|
||||
## Insert a vertex modifier to the mesh (at position `index`), and changes
|
||||
## the mesh data if necessary.
|
||||
self.vertex_modifiers.insert(modifier, index)
|
||||
if modifier.prepare_mesh.nonNil and self.data.nonNil:
|
||||
echo &"applying modifiers of {self.name} after it was already loaded"
|
||||
|
@ -463,10 +527,18 @@ proc insert_modifier*(self: Mesh, index: int, modifier: VertexModifier) =
|
|||
# self.update_signature()
|
||||
|
||||
proc remove_modifier*(self: Mesh, index: int) =
|
||||
## Removes a vertex modifier from the mesh at the given index.
|
||||
##
|
||||
## Note that currently if the modifier changed the mesh, it will remain
|
||||
## changed when removing the modifier.
|
||||
self.vertex_modifiers.delete(index)
|
||||
# self.update_signature()
|
||||
|
||||
proc remove_modifier*(self: Mesh, modifier: VertexModifier) =
|
||||
## Removes the given vertex modifier from the mesh.
|
||||
##
|
||||
## Note that currently if the modifier changed the mesh, it will remain
|
||||
## changed when removing the modifier.
|
||||
let index = self.vertex_modifiers.find(modifier)
|
||||
if index != -1:
|
||||
self.remove_modifier index
|
||||
|
@ -491,12 +563,25 @@ proc clone*(self: Mesh,
|
|||
return self.clone_impl(n)
|
||||
|
||||
proc sort_faces*(self: Mesh, camera_position: Vec3) =
|
||||
## TODO: unimplemented
|
||||
return
|
||||
|
||||
# TODO: support multiple index types with a generic?
|
||||
# NOTE: doing it using the triangles and not the original polygons might be
|
||||
# slightly inaccurate, unless mesh is trianglulated when baking
|
||||
proc generate_tangents*(self: MeshData, uv_layer_name, tangent_layer_name: string) =
|
||||
## Fills the given tangent attribute with tangent vectors based on the given
|
||||
## UV layer.
|
||||
##
|
||||
## Intended to be indirectly used by a mesh loader:
|
||||
##
|
||||
## * Reserving an attribute with a name starting by "tg_".
|
||||
## * With the same name as a UV layer minus the initial "uv_". For example
|
||||
## "tg_UVMap" will contain the tangents for "uv_UVMap".
|
||||
## * Then setting `mesh.generate_tangents` to `true`
|
||||
##
|
||||
## If the flag is set, this will be called automatically for each attribute
|
||||
## pair.
|
||||
if self.varrays.len != self.iarrays.len:
|
||||
raise newException(ValueError,
|
||||
"Function generate_tangents() doesn't support meshes without indices at the moment")
|
||||
|
@ -592,6 +677,8 @@ proc generate_tangents*(self: MeshData, uv_layer_name, tangent_layer_name: strin
|
|||
break
|
||||
|
||||
proc update_bounding_box*(self: Mesh) =
|
||||
## Calculates the local bounding box of the mesh and stores it in
|
||||
## `mesh.bound_box`.
|
||||
if self.data == nil or self.layout.len == 0:
|
||||
return
|
||||
# TODO: byte vertices
|
||||
|
@ -611,6 +698,10 @@ proc update_bounding_box*(self: Mesh) =
|
|||
self.center = mix(minv, maxv, 0.5)
|
||||
|
||||
proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) =
|
||||
## Debugging helper for mesh format loaders. It prints the values of each
|
||||
## attribute of vertices starting by index `starts` and ending before index
|
||||
## `ends` (not included). If you don't specify range, it will print the first
|
||||
## 10 vertices.
|
||||
if self.data.varrays[0].len == 0:
|
||||
return
|
||||
let varray = cast[ptr UncheckedArray[int8]](self.data.varrays[0][0].addr)
|
||||
|
@ -634,7 +725,20 @@ proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) =
|
|||
else: ""
|
||||
echo &"{attr.name} {data}"
|
||||
|
||||
proc add_polygonal_line*(self: Mesh, orig, dest: Vec3, width: float) =
|
||||
proc add_polygonal_line*(self: Mesh, orig, dest: Vec3, width: float,
|
||||
color = vec4(1), update = true) =
|
||||
## Adds a line made of a quad, from `orig` to `dest` with a given width. The
|
||||
## quad will face up the Z axis. If the line starts where the previous line
|
||||
## ended, it will change both ends to match (unless the angle is under ~37°).
|
||||
##
|
||||
## Optionally you can supply a color.
|
||||
##
|
||||
## If you're going to add many lines, set `update` to false, then call
|
||||
## `update_varray` at the end.
|
||||
##
|
||||
## It requires the mesh to have draw method TriangleStrip.
|
||||
when not defined(release):
|
||||
assert self.draw_method == TriangleStrip, "Error: mesh draw_method should be TriangleStrip"
|
||||
let last = self.last_polyline_point
|
||||
let last_left = self.last_polyline_left
|
||||
let has_last = self.data.num_indices[0] != 0
|
||||
|
@ -650,20 +754,17 @@ proc add_polygonal_line*(self: Mesh, orig, dest: Vec3, width: float) =
|
|||
# may be too big, ignore it
|
||||
inleft = left
|
||||
if has_last and not (last ~= orig):
|
||||
## NaN polygons
|
||||
self.add_vertex(vec3(), vec4(0,0,0,1))
|
||||
self.add_vertex(vec3(), vec4(0,0,0,1))
|
||||
# ## degen tris
|
||||
# self.add_vertex(last, vec4(0,0,0,1))
|
||||
# self.add_vertex(last, vec4(0,0,0,1))
|
||||
# self.add_vertex(orig, vec4(0,0,0,1))
|
||||
# self.add_vertex(orig, vec4(0,0,0,1))
|
||||
## degen tris
|
||||
self.add_vertex(last - last_left, color)
|
||||
self.add_vertex(orig + inleft, color)
|
||||
|
||||
# self.data.num_indices[0] -= 2
|
||||
self.add_vertex(orig + inleft, vec4(0,0,0,1))
|
||||
self.add_vertex(orig - inleft, vec4(0,0,0,1))
|
||||
self.add_vertex(dest + left, vec4(0,0,0,1))
|
||||
self.add_vertex(dest - left, vec4(0,0,0,1))
|
||||
self.add_vertex(orig + inleft, color)
|
||||
self.add_vertex(orig - inleft, color)
|
||||
self.add_vertex(dest + left, color)
|
||||
self.add_vertex(dest - left, color)
|
||||
self.last_polyline_point = dest
|
||||
self.last_polyline_left = left
|
||||
self.data.update_varray()
|
||||
if update:
|
||||
self.data.update_varray()
|
||||
|
||||
|
|
|
@ -40,6 +40,7 @@ type Window* = ref object of RootObj
|
|||
import std/math
|
||||
import std/strformat
|
||||
import std/strutils
|
||||
import std/unicode
|
||||
import ./gl
|
||||
import ../screen
|
||||
import ../util
|
||||
|
@ -54,9 +55,20 @@ proc screen*(window: Window): Screen {.inline.} =
|
|||
proc `screen=`*(window: Window, screen1: Screen) {.inline.} =
|
||||
global_screen = screen1
|
||||
|
||||
type MyouCallbacks = object
|
||||
getdir: proc(app: pointer, buf: ptr char, len: int32, which: int8): int32 {.cdecl.}
|
||||
getbundlefile: proc(app: pointer, buf: ptr char, len: int32, path: cstring): int32 {.cdecl,gcsafe.}
|
||||
closeapp: proc(app: pointer) {.cdecl.}
|
||||
|
||||
{.emit:"void *global_myou_app_pointer, *global_myou_app_callbacks;".}
|
||||
var app_pointer {.importc:"global_myou_app_pointer".}: pointer
|
||||
var callbacks0 {.importc:"global_myou_app_callbacks".}: pointer
|
||||
template callbacks: untyped = cast[ptr MyouCallbacks](callbacks0)
|
||||
|
||||
proc NimMain() {.importc.}
|
||||
proc myouEngineCreate() {.exportc,cdecl,stackTrace:off.} =
|
||||
proc myouEngineCreate(app: pointer, cbs: ptr MyouCallbacks) {.exportc,cdecl,stackTrace:off.} =
|
||||
app_pointer = app
|
||||
callbacks0 = cbs
|
||||
try:
|
||||
NimMain()
|
||||
echo "nimmain end"
|
||||
|
@ -75,15 +87,21 @@ proc myouEngineOnPause() {.exportc,cdecl.} =
|
|||
proc myouEngineOnResume() {.exportc,cdecl.} =
|
||||
discard
|
||||
|
||||
proc myouEngineOnSurfaceCreated(w,h,r: int32) {.exportc,cdecl.} =
|
||||
proc myouEngineOnSurfaceCreated(w,h,r: int32, scale: float32, top,right,bottom,left: float32) {.exportc,cdecl.} =
|
||||
assert global_screen != nil
|
||||
if global_screen != nil:
|
||||
global_screen.resize(w,h,r.int8)
|
||||
global_screen.display_scale = scale
|
||||
when not defined(myouUseFakeFrameInset):
|
||||
global_screen.frame_inset = FrameInset(top: top*scale, right: right*scale, bottom: bottom*scale, left: left*scale)
|
||||
global_screen.engine.renderer.initialize()
|
||||
|
||||
proc myouEngineOnSurfaceChanged(w,h,r: int32) {.exportc,cdecl.} =
|
||||
proc myouEngineOnSurfaceChanged(w,h,r: int32, scale: float32, top,right,bottom,left: float32) {.exportc,cdecl.} =
|
||||
if global_screen != nil:
|
||||
global_screen.resize(w,h,r.int8)
|
||||
global_screen.display_scale = scale
|
||||
when not defined(myouUseFakeFrameInset):
|
||||
global_screen.frame_inset = FrameInset(top: top*scale, right: right*scale, bottom: bottom*scale, left: left*scale)
|
||||
|
||||
proc make_window*(width, height: int32, title: string): Window =
|
||||
nil
|
||||
|
@ -146,11 +164,16 @@ proc start_platform_main_loop*(engine1: MyouEngine, main_loop1: proc(self: MyouE
|
|||
engine = engine1
|
||||
main_loop = main_loop1
|
||||
|
||||
proc myouEngineOnFrame() {.exportc,cdecl.} =
|
||||
type MyouEngineFrameResult = object
|
||||
needsKeyboard: uint8
|
||||
var frame_result: MyouEngineFrameResult
|
||||
|
||||
proc myouEngineOnFrame(): MyouEngineFrameResult {.exportc,cdecl.} =
|
||||
if engine == nil:
|
||||
return
|
||||
try:
|
||||
engine.main_loop()
|
||||
return frame_result
|
||||
except Exception as e:
|
||||
for line in e.getStackTrace.split '\n':
|
||||
echo line
|
||||
|
@ -168,5 +191,69 @@ proc platform_switch_screen*(screen: Screen): bool {.inline.} =
|
|||
discard
|
||||
|
||||
proc myouSetKeyboardVisible*(show: bool) =
|
||||
assert false, "TODO: myouSetKeyboardVisible in generic"
|
||||
frame_result.needsKeyboard = show.uint8
|
||||
|
||||
proc myouGetDocumentsDir*(): string =
|
||||
result.setLen 512
|
||||
let l = callbacks.getdir(app_pointer, result[0].addr, result.len.int32, 0)
|
||||
result.setLen l
|
||||
if result.startswith "file://":
|
||||
result = result[7 .. ^1]
|
||||
if result.endswith "/":
|
||||
result.setlen(result.len - 1)
|
||||
|
||||
proc myouGetCacheDir*(): string =
|
||||
result.setLen 512
|
||||
let l = callbacks.getdir(app_pointer, result[0].addr, result.len.int32, 1)
|
||||
result.setLen l
|
||||
if result.startswith "file://":
|
||||
result = result[7 .. ^1]
|
||||
if result.endswith "/":
|
||||
result.setlen(result.len - 1)
|
||||
|
||||
proc myouGetTmpDir*(): string =
|
||||
result.setLen 512
|
||||
let l = callbacks.getdir(app_pointer, result[0].addr, result.len.int32, 2)
|
||||
result.setLen l
|
||||
if result.startswith "file://":
|
||||
result = result[7 .. ^1]
|
||||
if result.endswith "/":
|
||||
result.setlen(result.len - 1)
|
||||
|
||||
proc myouGetBundledFilePath*(path: string): string {.gcsafe.} =
|
||||
result.setLen 512
|
||||
let l = callbacks.getbundlefile(app_pointer, result[0].addr, result.len.int32, path.cstring)
|
||||
result.setLen l
|
||||
if result.startswith "file://":
|
||||
result = result[7 .. ^1]
|
||||
|
||||
|
||||
proc myouOnKeyEvent(keyCode: int32, mods: int32, pressed: int32) {.exportc,cdecl.} =
|
||||
let key = cast[KeyCode](keyCode)
|
||||
let shiftKey = (mods and (1 shl 17)).bool
|
||||
let metaKey = (mods and (2 shl 17)).bool
|
||||
let altKey = (mods and (4 shl 17)).bool
|
||||
let ctrlKey = (mods and (8 shl 17)).bool
|
||||
for cb in global_screen.key_callbacks:
|
||||
cb(KeyEvent(
|
||||
pressed: pressed != 0,
|
||||
repeat: false,
|
||||
shiftKey: shiftKey, ctrlKey: ctrlKey, altKey: altKey, metaKey: metaKey,
|
||||
key: key,
|
||||
))
|
||||
if global_screen.break_current_callbacks:
|
||||
global_screen.break_current_callbacks = false
|
||||
break
|
||||
|
||||
proc myouOnCharEvent(str: cstring) {.exportc,cdecl.} =
|
||||
for rune in ($str).toRunes:
|
||||
let codepoint = cast[uint32](rune)
|
||||
for cb in global_screen.char_callbacks:
|
||||
cb(codepoint)
|
||||
if global_screen.break_current_callbacks:
|
||||
global_screen.break_current_callbacks = false
|
||||
break
|
||||
|
||||
proc myouCloseMobileApp*() =
|
||||
callbacks.closeapp(app_pointer)
|
||||
|
||||
|
|
|
@ -60,6 +60,12 @@ template to_rotation(o: GLFMInterfaceOrientation): int8 =
|
|||
of GLFMInterfaceOrientationLandscapeLeft: 3
|
||||
else: 0
|
||||
|
||||
proc update_inset(window: Window) {.inline.} =
|
||||
let screen = window.screen
|
||||
var top, right, bottom, left = 0.0
|
||||
window.glfmGetDisplayChromeInsets(top.addr, right.addr, bottom.addr, left.addr)
|
||||
screen.frame_inset = FrameInset(top:top, right:right, bottom:bottom, left:left)
|
||||
|
||||
proc `screen=`*(window: Window, screen: Screen) {.inline.} =
|
||||
window.glfmSetUserData cast[pointer](screen)
|
||||
if screen == nil:
|
||||
|
@ -69,9 +75,15 @@ proc `screen=`*(window: Window, screen: Screen) {.inline.} =
|
|||
let orientation = window.glfmGetInterfaceOrientation()
|
||||
echo &"resizing {width} {height}"
|
||||
window.screen.resize(width.int32, height.int32)
|
||||
window.update_inset()
|
||||
window.screen.display_scale = scale
|
||||
window.glfmSetOrientationChangedFunc proc(window: Window, orientation: GLFMInterfaceOrientation) {.cdecl.} =
|
||||
echo &"orientation {orientation.to_rotation}"
|
||||
window.screen.resize(window.screen.width, window.screen.height, orientation.to_rotation)
|
||||
# TODO: Check if this is being called in sensorLandscape mode.
|
||||
# window.glfmSetDisplayChromeInsetsChangedFunc proc (display: Window, top, right, bottom, left: float) {.cdecl.} =
|
||||
# echo "insets:"
|
||||
# dump (top, right, bottom, left)
|
||||
# TODO: on emscripten there's no way to distinguish between
|
||||
# multi touch and right/middle mouse clicks!!
|
||||
# TODO: maybe add a couple of listeners with EM_ASM to know
|
||||
|
@ -83,8 +95,11 @@ proc `screen=`*(window: Window, screen: Screen) {.inline.} =
|
|||
var width, height: cint
|
||||
glfmGetDisplaySize(window, width.addr, height.addr)
|
||||
let orientation = window.glfmGetInterfaceOrientation()
|
||||
let scale = window.glfmGetDisplayScale()
|
||||
echo &"sizing {width} {height}"
|
||||
screen.resize(width.int32, height.int32, orientation.to_rotation)
|
||||
window.update_inset()
|
||||
window.screen.display_scale = scale
|
||||
|
||||
{.emit:"void* global_glfm_window;".}
|
||||
var window {.importc:"global_glfm_window".}: Window
|
||||
|
@ -309,8 +324,17 @@ proc myouAndroidGetJniEnv*(): pointer =
|
|||
# Ussing offsets from ANativeActivity
|
||||
let vm = cast[ptr ptr array[7, pointer]](activity[1])
|
||||
# Using offsets from jni.h
|
||||
let GetEnv = cast[proc(vm: pointer, penv: var pointer, version: int32): int32 {.cdecl, gcsafe.}](vm[][6])
|
||||
discard GetEnv(vm, result, 0x00010006'i32)
|
||||
let getEnv = cast[proc(vm: pointer, penv: var pointer, version: int32): int32 {.cdecl, gcsafe.}](vm[][6])
|
||||
let attachCurrentThread = cast[proc(vm: pointer, penv: ptr pointer, args: pointer): int32 {.cdecl, gcsafe.}](vm[][4])
|
||||
let status = getEnv(vm, result, 0x00010006'i32)
|
||||
if status == -2: # JNI_EDETACHED
|
||||
# Attach thread to VM
|
||||
if attachCurrentThread(vm, result.addr, nil) != 0:
|
||||
echo "could not attach thread to VM"
|
||||
result = nil
|
||||
elif status != 0: # JNI_OK
|
||||
result = nil
|
||||
# TODO: do we ever need to detach a thread from the VM?
|
||||
|
||||
proc myouAndroidGetInternalDataPath*(): string =
|
||||
when defined(android):
|
||||
|
@ -322,5 +346,40 @@ proc myouAndroidGetExternalDataPath*(): string =
|
|||
let activity = cast[ptr array[6, cstring]](window.glfmAndroidGetActivity())
|
||||
return $activity[5]
|
||||
|
||||
when defined(android):
|
||||
proc AAssetManager_open(asset_manager: pointer, filename: cstring, mode: int32): pointer {.importc,cdecl.}
|
||||
proc AAsset_close(asset: pointer) {.importc,cdecl.}
|
||||
proc AAsset_getBuffer(asset: pointer): pointer {.importc,cdecl.}
|
||||
proc AAsset_getLength64(asset: pointer): int64 {.importc,cdecl.}
|
||||
proc ANativeActivity_finish(activity: pointer) {.importc,cdecl.}
|
||||
|
||||
type myouAAssetObj = object
|
||||
asset: pointer
|
||||
p*: pointer
|
||||
len*: int
|
||||
|
||||
type myouAAsset* = ref myouAAssetObj
|
||||
|
||||
proc `=destroy`(x: var myouAAssetObj) =
|
||||
if x.asset != nil:
|
||||
AAsset_close(x.asset)
|
||||
x.asset = nil
|
||||
|
||||
proc myouAndroidAPKFilePointerLength*(path: string): myouAAsset =
|
||||
let activity = cast[ptr array[9, pointer]](window.glfmAndroidGetActivity())
|
||||
# Ussing offsets from ANativeActivity, assuming all pointers are aligned
|
||||
let asset_manager = activity[8]
|
||||
let asset = AAssetManager_open(asset_manager, path.cstring, 3) # 3 = buffer mode
|
||||
if asset == nil:
|
||||
echo "not found in APK: ", path
|
||||
return myouAAsset()
|
||||
let p = AAsset_getBuffer(asset)
|
||||
let len = AAsset_getLength64(asset).int
|
||||
myouAAsset(asset: asset, p: p, len: len)
|
||||
|
||||
proc myouSetKeyboardVisible*(show: bool) =
|
||||
window.glfmSetKeyboardVisible(show)
|
||||
|
||||
proc myouCloseMobileApp*() =
|
||||
ANativeActivity_finish(window.glfmAndroidGetActivity())
|
||||
quit()
|
||||
|
|
|
@ -50,6 +50,8 @@ when compileOption("threads"):
|
|||
from loadable import terminateLoadableWorkerThreads
|
||||
from ../gpu_formats/texture_optimize import terminateTextureWorkerThreads
|
||||
|
||||
func c_strstr(haystack, needle: cstring): cstring {.importc: "strstr", header: "<string.h>".}
|
||||
|
||||
proc screen*(window: Window): Screen {.inline.} =
|
||||
cast[Screen](window.getWindowUserPointer)
|
||||
|
||||
|
@ -211,6 +213,10 @@ proc init_graphics*(engine: MyouEngine, width, height: int32, title: string,
|
|||
if max_messages == 0:
|
||||
return
|
||||
# dump (source, etype, id, severity, length)
|
||||
when defined(myouForceAstc):
|
||||
# Supress warnings when forcing astc on desktop
|
||||
if c_strstr(message, "emulating compressed format") != nil:
|
||||
return
|
||||
echo getStackTrace().rsplit('\n',3)[1]
|
||||
echo "OpenGL error: ", message
|
||||
max_messages -= 1
|
||||
|
|
|
@ -96,7 +96,7 @@ proc initScene*(self: Scene, engine: MyouEngine, name: string = "Scene",
|
|||
while self.name in engine.scenes:
|
||||
collision_seq += 1
|
||||
self.name = name & "$" & $collision_seq
|
||||
engine.new_scenes[self.name] = self
|
||||
engine.new_del_scenes[self.name] = self
|
||||
self.mesh_passes.setLen 3
|
||||
self.world = newWorld(self)
|
||||
self.background_color = vec4(0, 0, 0, 1)
|
||||
|
@ -119,6 +119,7 @@ proc newScene*(engine: MyouEngine, name: string = "Scene",
|
|||
return initScene(result, engine, name, add_viewport_automatically)
|
||||
|
||||
proc set_ob_name*(self: Scene, ob: GameObject, name: string) =
|
||||
## Rename the object and update the tables that reference it.
|
||||
var n = name
|
||||
while n in self.engine.objects:
|
||||
collision_seq += 1
|
||||
|
@ -131,6 +132,7 @@ proc add_object*(self: Scene, ob: GameObject,
|
|||
name: string = ob.name,
|
||||
parent_name: string = "", parent_bone: string = "",
|
||||
auto_update_matrix: bool = ob.auto_update_matrix): GameObject {.discardable.} =
|
||||
## Add an object to the scene
|
||||
if ob.scene != nil:
|
||||
if ob.scene == self:
|
||||
return
|
||||
|
@ -191,6 +193,7 @@ proc add_object*(self: Scene, ob: GameObject,
|
|||
return ob
|
||||
|
||||
proc remove_object*(self: Scene, ob: GameObject, recursive: bool = true) =
|
||||
## Remove an object from the scene
|
||||
self.children.remove ob
|
||||
if ob.auto_update_matrix:
|
||||
self.auto_updated_children.remove ob
|
||||
|
@ -235,6 +238,8 @@ proc remove_object*(self: Scene, ob: GameObject, recursive: bool = true) =
|
|||
|
||||
proc make_parent*(self: Scene, parent: GameObject, child: GameObject,
|
||||
keep_transform: bool = true) =
|
||||
## Set the parent of an object. It calculates the transformation so it
|
||||
## remains the same in world space unless `keep_transform = false` is passed.
|
||||
assert parent != nil and child != nil, "Arguments 'parent' and 'child' can't be nil."
|
||||
if child.parent.nonNil:
|
||||
self.clear_parent(child, keep_transform)
|
||||
|
@ -281,8 +286,10 @@ proc make_parent*(self: Scene, parent: GameObject, child: GameObject,
|
|||
self.children_are_ordered = false
|
||||
|
||||
proc clear_parent*(self: Scene, child: GameObject, keep_transform = true) =
|
||||
let parent = child.parent
|
||||
if parent != nil:
|
||||
## Disconnects an object from its parent. It calculates the transformation so
|
||||
## it remains the same in world space unless `keep_transform = false` is
|
||||
## passed.
|
||||
if child.parent != nil:
|
||||
if keep_transform:
|
||||
let rotation_order = child.rotation_order
|
||||
let (position, rotation) = child.get_world_position_rotation
|
||||
|
@ -297,13 +304,16 @@ proc clear_parent*(self: Scene, child: GameObject, keep_transform = true) =
|
|||
scale.y = world_matrix[1].xyz.length.copy_sign wm3[1,1]
|
||||
scale.z = world_matrix[2].xyz.length.copy_sign wm3[2,2]
|
||||
child.set_rotation_order(rotation_order)
|
||||
parent.children.remove child
|
||||
child.parent.children.remove child
|
||||
child.parent = nil
|
||||
child.parent_bone_index = -1
|
||||
child.matrix_parent_inverse = mat4()
|
||||
return
|
||||
|
||||
proc reorder_children*(self: Scene) =
|
||||
## Ensures that the order of children objects have parents before children.
|
||||
## To be used by scene loaders that may add objects in any order.
|
||||
|
||||
# TODO: Only the objects marked as unordered need to be resolved here!
|
||||
# (make a new list and append to children)
|
||||
self.auto_updated_children.set_len 0
|
||||
|
@ -320,6 +330,8 @@ proc reorder_children*(self: Scene) =
|
|||
self.children_are_ordered = true
|
||||
|
||||
proc update_all_matrices*(self: Scene) =
|
||||
## Calculates the matrices of all non-static objects and bones. Called
|
||||
## automatically before rendering.
|
||||
if self.children_are_ordered == false:
|
||||
self.reorder_children()
|
||||
# TODO: do this only for visible and modified objects
|
||||
|
@ -337,11 +349,15 @@ proc update_all_matrices*(self: Scene) =
|
|||
ob.update_matrices()
|
||||
|
||||
proc set_objects_auto_update_matrix*(self: Scene, objects: seq[GameObject], auto_update: bool) =
|
||||
## Adds or removes the specified objects from the auto update matrix list.
|
||||
## I.e. those not in the list are static.
|
||||
for ob in objects:
|
||||
ob.auto_update_matrix = auto_update
|
||||
self.children_are_ordered = false
|
||||
|
||||
proc destroy*(self: Scene) =
|
||||
## Destroy the scene and all its resources.
|
||||
|
||||
# This may not be necessary. TODO: test
|
||||
for ob in self.children:
|
||||
if ob.is_mesh:
|
||||
|
@ -363,8 +379,7 @@ proc destroy*(self: Scene) =
|
|||
for cube in self.cubemaps:
|
||||
cube.destroy()
|
||||
self.cubemaps = @[]
|
||||
self.engine.scenes.del(self.name)
|
||||
self.engine.new_scenes.del(self.name)
|
||||
self.engine.new_del_scenes[self.name] = nil
|
||||
# bound textures can linger
|
||||
unbindAllTextures()
|
||||
# probably none of this is actually necessary
|
||||
|
@ -378,17 +393,21 @@ proc destroy*(self: Scene) =
|
|||
self.mesh_passes = @[]
|
||||
|
||||
proc enable_render*(self: Scene) =
|
||||
## Enable rendering of the scene
|
||||
self.enabled = true
|
||||
|
||||
proc enable_physics*(self: Scene) =
|
||||
## Enable the phyisics in the scene
|
||||
if self.world != nil:
|
||||
self.world.enabled = true
|
||||
|
||||
proc enable_all*(self: Scene) =
|
||||
## Enable rendering and physics of the scene.
|
||||
self.enable_render()
|
||||
self.enable_physics()
|
||||
|
||||
proc new_gameobject*(self: Scene, name: string): GameObject =
|
||||
## Create a GameObject and add it to the scene.
|
||||
return self.engine.new_gameobject(name=name, scene=self)
|
||||
|
||||
proc new_mesh*(self: Scene, name: string,
|
||||
|
@ -402,10 +421,32 @@ proc new_mesh*(self: Scene, name: string,
|
|||
index_array: seq[uint16] = @[],
|
||||
pass: int32 = 0,
|
||||
): Mesh =
|
||||
## Create a new Mesh object and add it to this scene.
|
||||
##
|
||||
## The most common draw methods are `Triangles` (the default), `Points`,
|
||||
## `Lines`, and `TriangleStrip` (for `add_polygonal_line()`).
|
||||
##
|
||||
## If you supply a layout, it will be used. Otherwise a layout will be
|
||||
## created from `common_attributes`, which by default is `{vertex, color}`.
|
||||
## The available common attributes are `{vertex, color, normal, uv}` and they
|
||||
## will be added in that order.
|
||||
##
|
||||
## You can give a `vertex_count` to allocate a mesh with capacity for that
|
||||
## amount of vertices, but you can always resize it later. Meant to be used
|
||||
## with `add_vertex()` and `add_polygonal_line`
|
||||
##
|
||||
## If you give a vertex array and an index array, they will be used directly
|
||||
## as GPU buffers. If you only supply the vertex array, indices will
|
||||
## implicitely be sequential.
|
||||
self.engine.new_mesh(name, self, draw_method, common_attributes, layout,
|
||||
skip_upload, vertex_count, vertex_array, index_array, pass)
|
||||
|
||||
proc set_active_camera*(self: Scene, camera: Camera) =
|
||||
## Change the active camera of the scene, and if there are no viewports in
|
||||
## the main screen, create one.
|
||||
|
||||
# TODO: change the camera in the viewports that use it as well.
|
||||
# TODO: should only one viewport allowed per camera?
|
||||
self.active_camera = camera
|
||||
if camera.scene != self:
|
||||
let name = if camera.name != "": camera.name else: "Camera"
|
||||
|
@ -416,6 +457,9 @@ proc set_active_camera*(self: Scene, camera: Camera) =
|
|||
return
|
||||
|
||||
proc calculate_max_lights_and_cubemaps*(self: Scene) =
|
||||
## Change the limits for shader lighting data (number of lights of each type,
|
||||
## cubemaps, etc.) depending on the existing objects and settings. It also
|
||||
## resets the shaders.
|
||||
self.max_point_lights = 0
|
||||
self.max_sun_lights = 0
|
||||
self.max_spot_lights = 0
|
||||
|
@ -445,6 +489,10 @@ proc calculate_max_lights_and_cubemaps*(self: Scene) =
|
|||
# echo &"calculated max cubemaps {self.max_cubemaps}"
|
||||
|
||||
proc get_lighting_UBOs*(self: Scene): seq[UBO] =
|
||||
## Get all the Uniform Buffer Objects (UBOs) with lighting information used
|
||||
## in shaders. You need to pass it when creating a custom material that uses
|
||||
## them. You also need to pass the GLSL code returned by `get_lighting_code`_.
|
||||
|
||||
# echo &"getting UBOs {self.max_point_lights} {self.max_sun_lights}"
|
||||
|
||||
if self.lighting_UBOs.len == 0:
|
||||
|
@ -475,6 +523,8 @@ proc get_lighting_UBOs*(self: Scene): seq[UBO] =
|
|||
|
||||
|
||||
proc get_lighting_code_defines*(self: Scene): seq[string] =
|
||||
## Returns the macro defines used in materials with the current limits. Don't
|
||||
## use it. It is added automatically.
|
||||
@[
|
||||
"#define MAX_POINT_LIGHTS " & $(if self.isNil: 0 else: self.max_point_lights),
|
||||
"#define MAX_SUN_LIGHTS " & $(if self.isNil: 0 else: self.max_sun_lights),
|
||||
|
@ -485,6 +535,9 @@ proc get_lighting_code_defines*(self: Scene): seq[string] =
|
|||
]
|
||||
|
||||
proc get_lighting_code*(self: Scene): string =
|
||||
## Returns the GLSL code to be able to use the UBOs given by
|
||||
## `get_lighting_UBOs`_. It includes the definition of the UBOs, texture
|
||||
## uniforms, and shadow map functions.
|
||||
var code: seq[string]
|
||||
code.add dedent """
|
||||
#define light_position pos.xyz
|
||||
|
@ -548,7 +601,7 @@ proc get_lighting_code*(self: Scene): string =
|
|||
uniform samplerCube cube_maps[MAX_CUBE_MAPS];
|
||||
#endif
|
||||
struct SH9 {
|
||||
vec3 c[9];
|
||||
vec4 c[9];
|
||||
};
|
||||
|
||||
#ifndef MAX_SPHERICAL_HARMONICS
|
||||
|
@ -567,6 +620,7 @@ proc get_lighting_code*(self: Scene): string =
|
|||
return code.join "\n"
|
||||
|
||||
proc update_lights*(self: Scene) =
|
||||
## Update the lighting UBOs. This is called automatically during rendering.
|
||||
if self.lighting_UBOs.len == 0:
|
||||
return
|
||||
var point, sun, spot, area = 0
|
||||
|
@ -627,7 +681,9 @@ proc update_lights*(self: Scene) =
|
|||
self.sun_light_UBO.update()
|
||||
|
||||
proc sort_cubemaps*(self: Scene) =
|
||||
# sort by volume
|
||||
## Sort cubemaps by volume, from largest to smallest. The shaders expect this
|
||||
## order. You don't need to call this, it is called automatically.
|
||||
|
||||
# TODO: avoid this allocation?
|
||||
var volumes = newSeqOfCap[(float32, CubemapProbe)](self.cubemap_probes.len)
|
||||
for probe in self.cubemap_probes:
|
||||
|
@ -643,7 +699,8 @@ proc sort_cubemaps*(self: Scene) =
|
|||
# or share depth buffers?
|
||||
|
||||
proc ensure_cubemaps*(self: Scene) =
|
||||
# echo "ensuring cubemaps"
|
||||
## Creates any missing cube maps. Called automatically.
|
||||
|
||||
let res = self.cubemap_resolution
|
||||
if res == 0:
|
||||
return
|
||||
|
@ -676,10 +733,13 @@ proc ensure_cubemaps*(self: Scene) =
|
|||
# echo &"made {self.cubemaps.len} cubemaps"
|
||||
|
||||
proc render_all_cubemaps*(self: Scene, use_roughness_prefiltering: bool, mipmap_shader: Material = nil) =
|
||||
## Render all cubemaps of the scene regardless of whether they need updating
|
||||
## or not.
|
||||
if self.cubemap_UBO == nil:
|
||||
return
|
||||
if self.world_material != nil:
|
||||
self.render_background_cubemap(use_roughness_prefiltering, mipmap_shader, upload_UBO = false)
|
||||
# TODO: Don't do this if objects haven't changed!
|
||||
self.sort_cubemaps()
|
||||
for probe in self.cubemap_probes:
|
||||
probe.render_cubemap(use_roughness_prefiltering, mipmap_shader)
|
||||
|
|
|
@ -52,6 +52,8 @@ import ./input
|
|||
import ./util
|
||||
|
||||
proc newScreen*(engine: MyouEngine, width, height: int32, title: string): Screen =
|
||||
## Creates a new screen or window. The first one is created by the engine for you.
|
||||
|
||||
result = new Screen
|
||||
result.engine = engine
|
||||
result.width = width
|
||||
|
@ -69,13 +71,20 @@ proc newScreen*(engine: MyouEngine, width, height: int32, title: string): Screen
|
|||
result.pre_draw = proc(self: Screen) = discard
|
||||
result.post_draw = proc(self: Screen) = discard
|
||||
result.frame_interval = 1
|
||||
result.display_scale = 1.0
|
||||
|
||||
proc destroy*(self: Screen) =
|
||||
## Destroys the screen/window and all its resources. If you destroy the main
|
||||
## screen, the first screen created after it will become the main screen. If
|
||||
## there are no screens left, the engine will exit.
|
||||
|
||||
self.engine.screens.remove self
|
||||
if self.engine.screen == self and self.engine.screens.len != 0:
|
||||
self.engine.screen = self.engine.screens[0]
|
||||
|
||||
proc resize*(self: Screen, width, height: int32, orientation = self.orientation) =
|
||||
## Resize the screen/window and all its viewports.
|
||||
|
||||
self.width = width
|
||||
self.height = height
|
||||
self.orientation = orientation
|
||||
|
@ -95,6 +104,8 @@ proc resize*(self: Screen, width, height: int32, orientation = self.orientation)
|
|||
f(self)
|
||||
|
||||
proc add_viewport*(self: Screen, camera: Camera) =
|
||||
## Add a viewport to the screen with a given camera.
|
||||
|
||||
let vp = new Viewport
|
||||
vp.camera = camera
|
||||
vp.clear_color = true
|
||||
|
@ -104,14 +115,19 @@ proc add_viewport*(self: Screen, camera: Camera) =
|
|||
self.resize(self.width, self.height)
|
||||
|
||||
proc `vsync=`*(self: Screen, vsync: bool) =
|
||||
## Change the vsync setting of the screen/window.
|
||||
cast[Window](self.window).set_vsync vsync
|
||||
|
||||
proc get_ray_direction*(viewport: Viewport, position: Vec2): Vec3 =
|
||||
## Calculates a vector that points from the camera towards the given screen
|
||||
## coordinates, in world space.
|
||||
let x = (position.x - viewport.rect_pix[0].float32) / viewport.rect_pix[2].float32
|
||||
let y = (position.y - viewport.rect_pix[1].float32) / viewport.rect_pix[3].float32
|
||||
return viewport.camera.get_ray_direction(x,y)
|
||||
|
||||
proc get_ray_direction_local*(viewport: Viewport, position: Vec2): Vec3 =
|
||||
## Calculates a vector that points from the camera towards the given screen
|
||||
## coordinates, in camera space.
|
||||
let x = (position.x - viewport.rect_pix[0].float32) / viewport.rect_pix[2].float32
|
||||
let y = (position.y - viewport.rect_pix[1].float32) / viewport.rect_pix[3].float32
|
||||
return viewport.camera.get_ray_direction_local(x,y)
|
||||
|
|
|
@ -205,7 +205,8 @@ vec4 diffuse_node(vec3 base, vec3 I, vec3 normal, vec3 pos){
|
|||
return vec4(color, 1.0);
|
||||
}
|
||||
|
||||
vec4 principled_node(vec3 base, vec3 I, vec3 normal, vec3 pos, float metallic, float roughness, float ior){
|
||||
vec4 principled_node(vec3 base, vec3 I, vec3 normal, vec3 pos, float metallic,
|
||||
float roughness, float ior, float alpha){
|
||||
vec3 N = normal;
|
||||
#if FIX_BORDERS
|
||||
float dotNI = dot(N, I);
|
||||
|
@ -286,14 +287,14 @@ vec4 principled_node(vec3 base, vec3 I, vec3 normal, vec3 pos, float metallic, f
|
|||
// these look good...
|
||||
vec3 env_specular = sample_environment(reflect(-I,N), pos, roughness) * (1.0 - roughness);
|
||||
#if MAX_SPHERICAL_HARMONICS
|
||||
vec3 env_diffuse = get_sh9(N, SH9_coefficients[0]) * 0.3;
|
||||
vec3 env_diffuse = get_sh9(N, 0) * 0.5;
|
||||
#else
|
||||
vec3 env_diffuse = sample_environment(N, pos, 1.0) * 0.1;
|
||||
#endif
|
||||
// color += mix(env_diffuse, env_specular, F);
|
||||
// adding instead of mixing because F is already in diffuse
|
||||
color += diffuse * env_diffuse + F * env_specular;
|
||||
return vec4(color, 1.0);
|
||||
return vec4(color, alpha);
|
||||
}
|
||||
|
||||
vec3 refraction_dominant_dir(vec3 N, vec3 V, float roughness, float ior)
|
||||
|
|
|
@ -3,18 +3,19 @@
|
|||
|
||||
#if MAX_SPHERICAL_HARMONICS
|
||||
|
||||
vec3 get_sh9(vec3 direction, SH9 sh)
|
||||
vec3 get_sh9(vec3 direction, int sh_index)
|
||||
{
|
||||
vec3 dir = SH9_rotation * direction;
|
||||
return sh.c[0] * 0.282095
|
||||
+ sh.c[1] * 0.488603 * dir.y
|
||||
+ sh.c[2] * 0.488603 * dir.z
|
||||
+ sh.c[3] * 0.488603 * dir.x
|
||||
+ sh.c[4] * 1.092548 * dir.x * dir.y
|
||||
+ sh.c[5] * 1.092548 * dir.y * dir.z
|
||||
+ sh.c[6] * 0.315392 * (3.0 * dir.z * dir.z - 1.0)
|
||||
+ sh.c[7] * 1.092548 * dir.x * dir.z
|
||||
+ sh.c[8] * 0.546274 * (dir.x * dir.x - dir.y * dir.y)
|
||||
SH9 sh = SH9_coefficients[0];
|
||||
return sh.c[0].rgb * 0.282095
|
||||
+ sh.c[1].rgb * 0.488603 * dir.y
|
||||
+ sh.c[2].rgb * 0.488603 * dir.z
|
||||
+ sh.c[3].rgb * 0.488603 * dir.x
|
||||
+ sh.c[4].rgb * 1.092548 * dir.x * dir.y
|
||||
+ sh.c[5].rgb * 1.092548 * dir.y * dir.z
|
||||
+ sh.c[6].rgb * 0.315392 * (3.0 * dir.z * dir.z - 1.0)
|
||||
+ sh.c[7].rgb * 1.092548 * dir.x * dir.z
|
||||
+ sh.c[8].rgb * 0.546274 * (dir.x * dir.x - dir.y * dir.y)
|
||||
;
|
||||
}
|
||||
|
||||
|
|
|
@ -86,7 +86,7 @@ type
|
|||
glsl_version*: string
|
||||
cache_settings*: CacheSettings
|
||||
all_framebuffers*: seq[Framebuffer] ## private
|
||||
new_scenes*: Table[string, Scene] ## private
|
||||
new_del_scenes*: Table[string, Scene] ## private
|
||||
|
||||
# gameobject.nim
|
||||
|
||||
|
@ -125,6 +125,7 @@ type
|
|||
# probe_cube*: Probe
|
||||
# probe_planar*: Probe
|
||||
properties*: Table[string, JsonNode]
|
||||
action*: Action # TODO: remove when strips are implemented
|
||||
# animation_strips*: seq[AnimationStrip]
|
||||
name*: string
|
||||
original_name*: string
|
||||
|
@ -207,6 +208,7 @@ type
|
|||
layout*: AttributeList
|
||||
vertex_modifiers*: seq[VertexModifier]
|
||||
mesh_id*: int8
|
||||
shape_keys*: OrderedTable[string, float32]
|
||||
bone_index_maps*: seq[Table[int32,int32]]
|
||||
sort_sign*: SortSign
|
||||
# related_textures*: seq[unknown]
|
||||
|
@ -502,7 +504,6 @@ type
|
|||
UBO* = ref object
|
||||
renderer* {.cursor.}: RenderManager
|
||||
name*: string
|
||||
size32*: int
|
||||
byte_storage*: ArrRef[byte]
|
||||
buffer*: GPUBuffer
|
||||
binding_point*: int32
|
||||
|
@ -584,7 +585,7 @@ type
|
|||
shadowmap_location*: GLint
|
||||
cubemap_locations*: seq[GLint]
|
||||
|
||||
when not defined(release):
|
||||
when defined(myouStoreGLSL):
|
||||
vs_code*, fs_code*: string
|
||||
|
||||
# texture.nim
|
||||
|
@ -823,6 +824,39 @@ type
|
|||
cache_dir_bc*: string ## Cache directory for writing bc
|
||||
cache_dir_astc*: string ## Cache directory for writing astc
|
||||
|
||||
# action.nim
|
||||
|
||||
Action* = ref object
|
||||
manual_range*: bool
|
||||
frame_start*, frame_end*: float32
|
||||
channels*: OrderedTable[string, AnimationChannel]
|
||||
|
||||
ChannelType* = enum
|
||||
ChObject
|
||||
ChPose
|
||||
ChShape
|
||||
ChObProperty
|
||||
|
||||
ChannelProperty* = enum
|
||||
PropValue
|
||||
PropPosition
|
||||
PropRotationEuler
|
||||
PropRotationQuaternion
|
||||
PropScale
|
||||
|
||||
AnimationChannel* = ref object
|
||||
channel_type*: ChannelType
|
||||
name*: string
|
||||
property*: ChannelProperty
|
||||
property_name*: string
|
||||
index*: int
|
||||
points*: seq[BezierPoint]
|
||||
last_eval_point*: int
|
||||
|
||||
BezierPoint* = object
|
||||
# left_handle*: Vec2
|
||||
co*: Vec2
|
||||
# right_handle*: Vec2
|
||||
|
||||
# INCOMPLETE
|
||||
|
||||
|
@ -841,6 +875,9 @@ type
|
|||
DebugDraw* = ref object of RootObj
|
||||
Body* = ref object
|
||||
|
||||
FrameInset* = object
|
||||
top*, right*, bottom*, left*: float
|
||||
|
||||
Screen* = ref object of RootObj
|
||||
engine* {.cursor.}: MyouEngine
|
||||
width*, height*: int32
|
||||
|
@ -864,6 +901,8 @@ type
|
|||
resize_callbacks*: seq[proc(screen: Screen)] ## private
|
||||
platform_event_callbacks*: seq[proc(screen: Screen, e: PlatformEvent)] ## private
|
||||
break_current_callbacks*: bool ## private
|
||||
frame_inset*: FrameInset
|
||||
display_scale*: float
|
||||
|
||||
PlatformEvent* = enum
|
||||
PlatformPause
|
||||
|
@ -884,9 +923,15 @@ type
|
|||
rect*: (float32,float32,float32,float32)
|
||||
rect_pix*: (int32,int32,int32,int32)
|
||||
|
||||
VertexModifierCodeLines* = object
|
||||
uniform_lines*, body_lines*, post_transform_lines*: seq[string]
|
||||
extensions*: seq[string]
|
||||
|
||||
VertexModifier* = object of RootObj
|
||||
get_code*: proc(v: seq[Varying]): (seq[string], seq[string], seq[string], seq[string])
|
||||
get_code*: proc(v: seq[Varying]): VertexModifierCodeLines
|
||||
prepare_mesh*: proc(m: Mesh)
|
||||
ubo*: UBO
|
||||
update*: proc(m: Mesh)
|
||||
|
||||
AnimationStrip* = object
|
||||
|
||||
|
@ -903,6 +948,7 @@ type BlendLoader* = ref object of Loader
|
|||
cached_materials*: Table[(FNode, string, bool),
|
||||
(string, seq[Varying], OrderedTable[string, string], OrderedTable[string, TexturePixels])] ## private
|
||||
resource*: LoadableResource
|
||||
textures*: Table[string, Texture]
|
||||
|
||||
template enqueue*(renderer: RenderManager, fun: untyped) =
|
||||
## Run a proc after the renderer has been initialized.
|
||||
|
|
Loading…
Reference in a new issue