Compare commits

...

15 commits

Author SHA1 Message Date
45cc6dac00 Animation & blend file: Preliminary support for actions. 2024-12-16 21:34:55 +01:00
fc39a93a94 Blend file: Use alpha blending flag, add alpha in principled & teximage nodes. 2024-12-16 21:30:37 +01:00
11ef0fbd41 Generic platform: Add procs for reading assets and for key/character events. 2024-12-16 21:27:26 +01:00
b4c7061fc9 Shader library: Fix SH9 not working with some devices because of buggy vec3. 2024-12-16 21:24:42 +01:00
6d6c807ff2 Material: Add -d:myouStoreGLSL and setOverrideXXCode() to live edit shaders. 2024-12-16 21:21:18 +01:00
8155c53314 Blend loader: Add texture cache. 2024-12-16 21:17:43 +01:00
67307032da Textures: Add several defines and constructor options for compression, cache.
* Add defines `myouForceAstc`, `myouMinTextureChannels`, 
  `myouLoadUncompressedTextures` and `myouAllCacheFilesExist`.
* Suppress warning messages about emulation for `myouForceAstc`.
* Add argument `use_compression` to `newTexture` (default `true`)
2024-12-16 21:05:21 +01:00
0192db3f67 Implement shape keys, vertex modifier support. Fix related bugs. 2024-12-16 21:02:13 +01:00
12940dad1d Android: Add APK reading, closing app. Fix JNI env not attached to thread. 2024-12-04 13:32:01 +01:00
e0b02d2708 Screen: Add frame_inset and display_scale, and implement them in Android. 2024-12-04 13:11:09 +01:00
bb4f8e1c00 Change new_scenes to new_del_scenes to avoid issues when deleting a scene. 2024-11-30 00:53:26 +01:00
2caf78c88a Mesh: Fix vertex_count, vertex_array in newMesh. Fix add_polygonal_line() 2024-11-30 00:48:43 +01:00
893208f4c2 Add docstrings to gameobject, mesh, light, camera, scene, screen, attributes. 2024-11-30 00:17:06 +01:00
080d9bcc67 Loadable: add registerLogUriHandler() to get a log of loaded URIs. 2024-11-29 13:32:46 +01:00
6212b816f0 Loadable: Add registerCustomProtocol() for e.g. reading assets in mobile. 2024-11-29 13:30:32 +01:00
28 changed files with 952 additions and 160 deletions

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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()

View file

@ -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()

View file

@ -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

View file

@ -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

View file

@ -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.} =

View file

@ -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)

View file

@ -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.} =

View 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()

View file

@ -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:

View file

@ -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 =

View file

@ -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())

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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)

View file

@ -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()

View file

@ -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

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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)
;
}

View file

@ -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.