Compare commits

...

12 commits

16 changed files with 252 additions and 137 deletions

View file

@ -406,7 +406,7 @@ when compileOption("threads"):
proc workerThreadProc() {.thread.} =
# TODO: handle errors
while true:
let to_decode = decode_chan.recv()
var to_decode = decode_chan.recv()
if to_decode.tex == nil:
break
proc cb(tex: Texture, data: SliceMem[byte]) =
@ -415,7 +415,7 @@ when compileOption("threads"):
compressed_return_chan.send((callback: to_decode.callback_compressed, tex: tex, data: data, refdata: refdata))
let cb_out = if to_decode.callback_uncompressed != nil: cb else: nil
let cbc_out = if to_decode.callback_compressed != nil: cbc else: nil
loadOptimized(to_decode.tex, to_decode.slices, cb_out, cbc_out,
loadOptimized(move to_decode.tex, to_decode.slices, cb_out, cbc_out,
to_decode.flip, to_decode.min_channels)
decode_chan.open()

View file

@ -208,7 +208,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
@[
&"precision {precision} float;",
&"precision {precision} int;",
&"precision {precision} sampler2DShadow;",
&"precision {precision} sampler2DArrayShadow;",
]
else:
@[]
@ -438,6 +438,7 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
if success == 0:
let gl_log = get_shader_info_log(fragment_shader)
let error_msg = &"Error compiling fragment shader of material {material.name}\n{gl_log}"
echo error_msg
# console_error fragment
let lines = fragment.split("\n")
if "ERROR: 0:" in gl_log:
@ -452,8 +453,8 @@ proc initShader*(self: Shader, engine: MyouEngine, material: Material,
for i in max(1, line - 1000) ..< min(line + 4, lines.len):
console_error(&"{i} {lines[i - 1]}")
else:
for i in 1 ..< lines.len:
console_error(&"{i} {lines[i - 1]}")
for i,line in lines:
console_error(&"{i+1} {line}")
console_error(error_msg)
return
glGetProgramiv(prog, GL_LINK_STATUS, addr success)

View file

@ -260,7 +260,7 @@ proc bind_it*(texture: Texture, reserve_slot: static[int32] = -1, needs_active_t
# if old_tex.sampler_object != 0:
# glBindSampler(cast[GLuint](bound_unit), 0)
bound_textures[bound_unit] = nil
glBindTexture(texture.storage.target, texture.storage.tex.GLuint)
glBindTexture(texture.storage.target, GLuint(texture.storage.tex))
# if texture.sampler_object != 0:
# glBindSampler(bound_unit.GLuint, texture.sampler_object)
bound_textures[bound_unit] = texture
@ -554,8 +554,8 @@ proc getTexturePixels*(self: Texture): TexturePixels =
proc setMipmapRange*(self: Texture, first = 0, last = 1000) {.gcsafe.} =
self.bind_it(needs_active_texture=true)
self.mipmap_range = (first, last)
glTexParameteri(self.storage.target.GLenum, GL_TEXTURE_BASE_LEVEL, first.GLint);
glTexParameteri(self.storage.target.GLenum, GL_TEXTURE_MAX_LEVEL, last.GLint);
glTexParameteri(self.storage.target, GL_TEXTURE_BASE_LEVEL, first.GLint);
glTexParameteri(self.storage.target, GL_TEXTURE_MAX_LEVEL, last.GLint);
func vec3size*(self: Texture, mip_level = -1): Vec3 =
let depth = if self.tex_type == Tex2DArray: 1 else: self.depth

View file

@ -44,7 +44,7 @@ import std/strutils
import std/strformat
import std/bitops
import std/options
# import std/tables
import std/json
import vmath except Quat
import ../quat
@ -101,9 +101,9 @@ method openAssetFile*(self: BlendLoader, path: string) =
# self.on_destroy = OnDestroy(destructor: proc() = self.close())
self.blend_file_path = path
proc loadAsync(self: BlendLoader, callback: proc()) =
proc loadAsync(self: BlendLoader, callback: proc(err: string)) =
if self.blend_file != nil:
callback()
callback("")
else:
self.close()
self.resource = loadUri(self.blend_file_path,
@ -111,9 +111,7 @@ proc loadAsync(self: BlendLoader, callback: proc()) =
self.resource = nil
if ok:
self.blend_file = openBlendFile(self.blend_file_path, data.data, data.byte_len)
callback()
else:
raise IOError.newException err
callback(err)
)
type BlenderObTypes = enum
@ -265,6 +263,31 @@ proc makeMaterialAndTextures(self: BlendLoader;
echo "Material: ", bmat.id.name.str.strip2
raise e
proc idPropertiesToJsonTable(prop: FNode): Table[string, JsonNode] =
var prop = prop
while prop.valid:
let name = prop.name.str
let val = prop.data.val.i32[0]
var cstr: cstring = ""
if prop.data["pointer"].valid:
var p = prop.data["pointer"]
p.set_type("char")
cstr = p.cstr
case prop["type"].i8[0]
of 0: # string
result[name] = %($cstr)
of 1: # int
result[name] = %val
of 5: # array
let subtype = prop.subtype.i8[0]
of 8: # float
let f = cast[ptr float64](prop.data.val.i32[0].addr)[]
result[name] = %f
of 10: # bool
result[name] = %bool(val)
else: discard
prop = prop.next
proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, string) =
let name = obn.id.name.str.strip2
let data = obn.data
@ -482,6 +505,10 @@ proc loadObjectImpl(self: BlendLoader, scene: Scene, obn: FNode): (GameObject, s
ob.object_color = obn.col.f32.vec4
let prop = obn.id.properties
if prop.valid:
ob.properties = idPropertiesToJsonTable(prop.data.group.first)
return (ob, parent_name)
proc loadSceneImpl(self: BlendLoader, scn: FNode, scene: Scene) =
@ -573,9 +600,12 @@ proc get_active_scene_name(self: BlendLoader): string =
if scene.valid:
return scene.id.name.str[2 .. ^1]
method loadScene*(self: BlendLoader, name: string="", scene: Scene=nil, callback: proc(scene: Scene)) =
method loadScene*(self: BlendLoader, name: string="", scene: Scene=nil, callback: proc(err: string, scene: Scene)) =
assert self.blend_file_path != "", "Blend file is not loaded"
self.loadAsync proc() =
self.loadAsync proc(err: string) =
if err != "":
callback(err, nil)
return
assert self.blend_file != nil, "Error loading blend file " & self.blend_file_path
var name = name
if name == "":
@ -588,8 +618,19 @@ method loadScene*(self: BlendLoader, name: string="", scene: Scene=nil, callback
let was_first_scene = self.engine.scenes.len == 0
if scene == nil:
scene = self.engine.new_scene(name=name)
try:
self.loadSceneImpl(node, scene)
callback(scene)
except Exception as e:
for line in e.getStackTrace.split '\n':
echo line
echo getCurrentExceptionMsg()
scene.destroy()
callback(getCurrentExceptionMsg(), nil)
return
callback("", scene)
if scene.name notin self.engine.new_scenes:
# it was deleted
return
# TODO: when loading is async, move this stuff after loading has
# finished
if was_first_scene and not scene.enabled:

View file

@ -122,7 +122,7 @@ proc hash*(n: FNode): Hash =
const BASIC_TYPE_LENGTHS = [1, 1, 2, 2, 4, 4, 4, 4, 8, 8, 8, 0, 1]
template `[]`*(blocks: NamedBlocks, s: string): untyped = blocks[[s[0],s[1]]]
template `[]`*(blocks: NamedBlocks, s: string): untyped = blocks.getOrDefault([s[0],s[1]])
# iterator items*(s: NamedBlocks): seq[FNode] {.borrow.}
# iterator pairs*(s: NamedBlocks): (array[2, char], seq[FNode]) {.borrow.}

View file

@ -39,7 +39,7 @@ when defined(nimdoc):
method openAssetFile*(self: Loader, path: string) {.base.} =
discard
method loadScene*(self: Loader, name: string="", scene: Scene=nil,
callback: proc(scene: Scene)) {.base.} =
callback: proc(err: string, scene: Scene)) {.base.} =
discard
method loadImageImpl*(self: Loader) {.base.} =
discard

View file

@ -201,7 +201,7 @@ proc run*(self: MyouEngine) =
when not defined(nimdoc):
start_platform_main_loop(self, myou_main_loop)
proc loadScene*(self: MyouEngine, uri: string, callback: proc(scene: Scene), name = "", ext = "") =
proc loadScene*(self: MyouEngine, uri: string, callback: proc(err: string, scene: Scene), name = "", ext = "") =
let ext = if ext == "":
uri.rsplit('.', 1)[1]
else:

View file

@ -92,7 +92,8 @@ proc get_local_Z_vector*(self: GameObject): Vec3
proc get_world_X_vector*(self: GameObject): Vec3
proc get_world_Y_vector*(self: GameObject): Vec3
proc get_world_Z_vector*(self: GameObject): Vec3
proc get_dimensions*(self: GameObject): Vec3
proc get_local_dimensions*(self: GameObject): Vec3
proc get_world_dimensions*(self: GameObject): Vec3
proc get_world_scale*(self: GameObject): float
proc get_world_scale_vector*(self: GameObject): Vec3
proc get_local_size*(self: GameObject): float
@ -241,10 +242,12 @@ proc get_world_matrix*(self: GameObject): Mat4 =
return self.world_matrix
proc get_local_matrix*(self: GameObject): Mat4 =
var (x,y,z,w) = self.rotation.toTuple
# if self.rotation_order != Quaternion:
# let q = fromEulerOrder(self.rotation, self.rotation_order)
# let (x,y,z,w) = q.toTuple
var q = if self.rotation_order == Quaternion:
self.rotation
else:
to_quat(self.rotation.xyz, self.rotation_order)
q = normalize(q)
var (x,y,z,w) = q.toTuple
let scl = self.scale
# TODO: test negative scales
let pos = self.position
@ -390,6 +393,9 @@ proc clone*(self: GameObject,
scene: Scene=self.scene,
instance_body: bool=true
): GameObject =
## Makes a clone of the object and its children (unless `recursive` is
## false).
case self.otype:
of TGameObject:
var n = new GameObject
@ -438,9 +444,17 @@ proc clone_impl*(self: GameObject, n: var GameObject, recursive: bool,
return n
proc set_parent*(self: GameObject, parent: GameObject, keep_transform: bool=true) =
## Sets the parent of the object, while keeping the same transform in world
## space (unless you set `keep_transform` to false).
# TODO: there should be no distinction between this and parent_to
self.scene.make_parent(parent, self, keep_transform=keep_transform)
proc parent_to*(self: GameObject, parent: GameObject, keep_transform: bool=true) =
## Sets the parent of the object, while adding it to the same scene if
## needed, as well as keeping the same transform in world space (unless you
## set `keep_transform` to false).
# TODO move this to make_parent
if self.scene == nil:
parent.scene.add_object(self)
@ -450,12 +464,17 @@ proc clear_parent*(self: GameObject, keep_transform: bool=true) =
self.scene.clear_parent(self, keep_transform=keep_transform)
proc get_top_ancestor*(self: GameObject, top_level_parents: seq[GameObject] = @[]): GameObject =
## Find the topmost ancestor (e.g. parent of parent of parent...) or an
## object contained in `top_level_parents`, whatever happens first.
var ob = self
while ob.parent != nil and ob.parent notin top_level_parents:
ob = ob.parent
return ob
iterator children_recursive*(self: GameObject, include_self: static[bool] = false): GameObject =
## Iterate all the descendants of the object, i.e. its children, the
## children's children an so on.
# NOTE: This was written before the knowledge of closure iterators
# (by default iterators are inline and don't allow recursion)
when include_self:
@ -481,11 +500,20 @@ iterator children_recursive*(self: GameObject, include_self: static[bool] = fals
else:
break
template children_recursive_and_self*(self: GameObject): GameObject =
## Iterate all the descendants of the object, and include itself in the
## iteration.
children_recursive(self, include_self = true)
proc remove*(self: GameObject, recursive: bool = true) =
## Remove the object from the scene it is in, without destroying it.
## It will also remove its children unless you set `recursive` to false.
if self.scene.nonNil:
self.scene.remove_object(self, recursive)
proc destroy*(self: GameObject, recursive: bool = true) =
## Destroy the object and free all unused resources previously used by it.
## It will also destroy its children unless you set `recursive` to false.
if self.is_mesh and self.get_mesh.data.nonNil:
self.get_mesh.data.gpu_buffers_delete()
self.get_mesh.materials.setLen 0
@ -516,27 +544,41 @@ proc convert_bone_child_to_bone_parent*(self: GameObject) =
bone.parent_object = self
proc get_local_X_vector*(self: GameObject): Vec3 =
## Get the vector of the X axis of the object in local space.
return self.get_local_matrix[0].xyz
proc get_local_Y_vector*(self: GameObject): Vec3 =
## Get the vector of the Y axis of the object in local space.
return self.get_local_matrix[1].xyz
proc get_local_Z_vector*(self: GameObject): Vec3 =
## Get the vector of the Z axis of the object in local space.
return self.get_local_matrix[2].xyz
proc get_world_X_vector*(self: GameObject): Vec3 =
## Get the vector of the X axis of the object in world space.
return self.get_world_matrix[0].xyz
proc get_world_Y_vector*(self: GameObject): Vec3 =
## Get the vector of the Y axis of the object in world space.
return self.get_world_matrix[1].xyz
proc get_world_Z_vector*(self: GameObject): Vec3 =
## Get the vector of the Z axis of the object in world space.
return self.get_world_matrix[2].xyz
proc get_dimensions*(self: GameObject): Vec3 =
proc get_local_dimensions*(self: GameObject): Vec3 =
## Calculate the local dimensions of the object, i.e. the size of the
## bounding box with the scale of the object.
let (a,b) = self.bound_box
return (b-a) * self.scale
proc get_world_dimensions*(self: GameObject): Vec3 =
## Calculate the world dimensions of the object, i.e. the size of the
## bounding box in world scale.
let (a,b) = self.bound_box
return (b-a) * self.get_world_scale_vector
proc get_world_scale*(self: GameObject): float =
# TODO: use the diagonal of a sphere instead?
# i.e. length((wm*vec3(1).normalize,0).xyz)
@ -548,46 +590,46 @@ proc get_world_scale*(self: GameObject): float =
return s
proc get_world_scale_vector*(self: GameObject): Vec3 =
## Gets the world scale vector in local coordinates (i.e. relative to the
## current object orientation).
let rotation = self.get_world_rotation
let wm = self.world_matrix
let rot_inv = inverse(rotation)
var wm3 = wm.to_mat3
let wm3_b = rot_inv.to_mat3
wm3 = wm3_b * wm3
let scale_x_sign = copy_sign(1.0, wm3[0,0])
let scale_y_sign = copy_sign(1.0, wm3[1,1])
let scale_z_sign = copy_sign(1.0, wm3[2,2])
return vec3(
wm[0].xyz.length * scale_x_sign,
wm[1].xyz.length * scale_y_sign,
wm[2].xyz.length * scale_z_sign,
copy_sign(wm[0].xyz.length, wm3[0,0]),
copy_sign(wm[1].xyz.length, wm3[1,1]),
copy_sign(wm[2].xyz.length, wm3[2,2]),
)
proc get_local_size*(self: GameObject): float =
let d = self.get_dimensions()
## Calculates the size of the largest dimension of the object in local
## coordinates.
let d = self.get_local_dimensions()
return max(max(d.x, d.y), d.z)
proc set_local_size*(self: GameObject, size: SomeFloat) =
## Sets the size of the object in local cordinates, given by the desired
## largest dimension of the object. The other two dimensions are scaled
## proportionally. If the object dimensions are zero, no action is taken.
let current_size = self.get_local_size
if abs(current_size) > 1e-8:
self.scale = self.scale * size / current_size
return
proc get_world_size*(self: GameObject): float =
let d = self.get_dimensions()
let size = max(max(d.x, d.y), d.z)
if self.parent != nil:
let parent_z_scale = self.parent.get_world_Z_vector.length
return size * parent_z_scale
return size
## Calculates the size of the largest dimension of the object in world
## coordinates.
let d = self.get_world_dimensions()
return max(max(d.x, d.y), d.z)
proc set_world_size*(self: GameObject, size: SomeFloat) =
var size = size
if self.parent != nil:
let parent_z_scale = self.parent.get_world_Z_vector.length
if abs(parent_z_scale) > 1e-8:
size /= parent_z_scale
let current_size = self.get_local_size
## Sets the size of the object in world cordinates, given by the desired
## largest dimension of the object. The other two dimensions are scaled
## proportionally. If the object dimensions are zero, no action is taken.
let current_size = self.get_world_size
if abs(current_size) > 1e-8:
self.scale = self.scale * size / current_size
return
@ -600,11 +642,14 @@ proc set_name*(self: GameObject, name: string) =
self.scene.set_ob_name(self, name)
proc local_to_world*(self: GameObject, point: Vec3): Vec3 =
## Transforms a point in local coordinates to world coordinates.
return self.get_world_matrix * point
proc world_to_local*(self: GameObject, point: Vec3): Vec3 =
# TODO: check that determinant is not zero?
# does the compiler optimize multiple calls to .determinant?
let wm = self.get_world_matrix.inverse
return wm * point
## Transforms a point in world coordinates to local coordinates. If the
## object has a scale of 0, the point is not transformed.
let wm = self.get_world_matrix
if wm.determinant != 0.0:
return wm.inverse * point
return point

View file

@ -33,6 +33,7 @@
import ../types
import vmath except Quat
import ../util
import ../graphics/material
when defined(nimdoc):
type TYPES* = LightType | Light
@ -100,6 +101,9 @@ proc newLight*(engine: MyouEngine, name: string="",
scene.add_object(this, name=name)
return this
when defined(myouDebugShadows):
var debug_mesh: Mesh
proc configure_shadow*(self: Light,
camera: Camera = nil,
max_distance: float32 = 0.0,
@ -131,13 +135,16 @@ proc configure_shadow*(self: Light,
# TODO: instead of a ground plane, we can just exclude the largest
# convex object, as long as we can clamp the polygons outside the
# frustum. Can we avoid clipping by setting W?
self.scene.update_all_matrices()
var casters: seq[Vec3]
for ob in self.scene.children:
for ob in objects:
if not (ob.is_mesh and ob.visible):
continue
let me = ob.get_mesh
let bb = me.bound_box
if bb[0] == bb[1]:
continue
let world_dim = (ob.world_matrix * vec4(bb[1] - bb[0], 0.0)).xyz
var casts = true
if world_dim.z < 0.00001:
@ -155,9 +162,29 @@ 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
if casters.len == 0:
return
when defined(myouDebugShadows):
if debug_mesh.nonNil:
debug_mesh.destroy()
debug_mesh = self.engine.newMesh(vertex_count = 100, draw_method = Lines)
debug_mesh.materials.add self.engine.newSolidMaterial("green", vec4(0,1,0,1))
debug_mesh.visible = true
self.scene.add_object debug_mesh
let hull = quickhull(casters)
for f in hull:
debug_mesh.add_vertex f.points[0], vec4(1)
for v in f.points[1..^1]:
debug_mesh.add_vertex v, vec4(1)
debug_mesh.add_vertex v, vec4(1)
debug_mesh.add_vertex f.points[0], vec4(1)
debug_mesh.data.update_varray()
casters = quickhull_points(casters)
let shadow = newSimpleShadowManager(self, use_camera = false)
shadow.caster_bounding_points = casters

View file

@ -342,16 +342,18 @@ proc clear_vertices*(self: Mesh) =
self.data.num_indices[0] = 0
proc remove_vertex*(self: Mesh, index: int) =
# TODO: This is only valid for points mode
if not (index >= 0):
return
# -1 or undefined
let num_indices = self.data.num_indices
return # ignore -1
assert self.data.num_indices[0] > index, "Invalid index"
let stride = self.data.stride
# move the last vertex to the one in index
let pos0 = num_indices[0] * stride
let pos0 = self.data.num_indices[0] * stride
let pos1 = index * stride
var bytes = self.data.varrays[0].to(int8)
bytes.copyWithin(pos1, pos0, pos0 + stride)
for i in 0 ..< stride:
bytes[pos1+i] = bytes[pos0+i]
self.data.num_indices[0] -= 1
proc ensure_capacity*(self: Mesh, extra_elements: int) =
if self.data == nil:
@ -371,7 +373,7 @@ proc ensure_capacity*(self: Mesh, extra_elements: int) =
while fpos >= cap:
cap *= 2
var va = newArrRef[float32](cap)
varray.copy_bytes_to va
copyMem(va.toPointer, varray.toPointer, bytelen)
self.load_from_va_ia @[va]
self.data.num_indices[0] = current_index
@ -381,8 +383,8 @@ proc ensure_capacity*(self: Mesh, extra_elements: int) =
# let ilen = self.offsets[self.offsets.len - 1]
# let offset = (self.pack_offset or 0) + buffer_offset
# try:
# let va = makeSeq[float32](data, offset, vlen)
# let ia = makeSeq[uint16](data, offset + vlen * 4, ilen)
# let va = newSeq[float32](data, offset, vlen)
# let ia = newSeq[uint16](data, offset + vlen * 4, ilen)
# except Exception as e:
# let e = Error(&"Mesh {self.name} is corrupt")
# raise e
@ -518,7 +520,7 @@ proc generate_tangents*(self: MeshData, uv_layer_name, tangent_layer_name: strin
for idx in ia.to int32:
maxi = max(maxi, idx)
# dump (max_len, maxi)
var tangents = makeSeq[Vec3](max_len div stride_f.int)
var tangents = newSeq[Vec3](max_len div stride_f.int)
# for va, ia in zip(self.varrays, self.iarrays):
for i in 0 ..< self.varrays.len:
let va = self.varrays[i]
@ -627,7 +629,7 @@ proc debug_print_vertices*(self: Mesh, starts: int = 0, ends: int = 10) =
of Short:
($cast[ptr array[16, uint16]](varray[offset].addr)[]).split(',')[0 ..< attr.count].join(",") & "]"
# of HalfFloat:
# let a = makeSeq[uint16](varray.buffer, offset, attr.count)
# let a = newSeq[uint16](varray.buffer, offset, attr.count)
# @[read_f16(a[0]), read_f16(a[1]), read_f16(a[2])]
else: ""
echo &"{attr.name} {data}"

View file

@ -20,9 +20,11 @@ proc get_program_info_log*(program: GLuint): string =
glGetProgramInfoLog(program, logSize.GLsizei, nil, s.cstring)
return s
when compileOption("threads"):
let main_thread_id = getThreadID()
template handleError(x: untyped, t: string) =
when compileOption("threads"):
assert main_thread_id == getThreadID()
try:
x

View file

@ -342,6 +342,13 @@ proc set_objects_auto_update_matrix*(self: Scene, objects: seq[GameObject], auto
self.children_are_ordered = false
proc destroy*(self: Scene) =
# This may not be necessary. TODO: test
for ob in self.children:
if ob.is_mesh:
for m in ob.get_mesh.materials:
if m != nil:
for tex in m.textures.mvalues:
tex.destroy()
for ob in reversed(self.children):
ob.destroy(recursive=false)
self.world.destroy()
@ -351,7 +358,13 @@ proc destroy*(self: Scene) =
self.shadow_maps.destroy()
for ubo in self.lighting_UBOs:
ubo.destroy()
if self.background_cubemap != nil:
self.background_cubemap.destroy()
for cube in self.cubemaps:
cube.destroy()
self.cubemaps = @[]
self.engine.scenes.del(self.name)
self.engine.new_scenes.del(self.name)
# bound textures can linger
unbindAllTextures()
# probably none of this is actually necessary

View file

@ -116,6 +116,15 @@ proc get_ray_direction_local*(viewport: Viewport, position: Vec2): Vec3 =
let y = (position.y - viewport.rect_pix[1].float32) / viewport.rect_pix[3].float32
return viewport.camera.get_ray_direction_local(x,y)
proc get_pixels_at_depth*(viewport: Viewport, depth: float32): float32 =
## Returns the length of one world unit (usually one meter) in screen pixels,
## at a given depth.
# TODO: shift_x
let p = vec4(1.0, 0.0, -depth, 1.0)
let s = viewport.camera.projection_matrix * p
return viewport.rect_pix[2].float32 * 0.5 * s.x/s.w
proc clear_all_callbacks*(self: Screen) =
self.key_callbacks.setLen 0
self.mouse_button_callbacks.setLen 0

View file

@ -5,7 +5,7 @@ import ../graphics/framebuffer
import ../graphics/texture
import ../scene
const USE_SHADOW_SAMPLERS* = false
const USE_SHADOW_SAMPLERS* = true
when defined(nimdoc):
type TYPES* = ShadowManager | ShadowMapUniform
@ -38,6 +38,12 @@ proc updateShadowStorage*(scene: Scene) =
let fb_size = scene.shadow_map_resolution
assert color_channels <= 4
if scene.shadow_maps != nil:
# TODO: we're assuming that if it exists it has all the same parameters
# and that only resolution and number of layers will change
if scene.shadow_maps.width == fb_size and
scene.shadow_maps.layer_count == count:
scene.calculate_max_lights_and_cubemaps()
return
scene.shadow_maps.destroy()
scene.shadow_maps = scene.engine.newFramebuffer(fb_size, fb_size,
if color_channels != 0:

View file

@ -97,6 +97,8 @@ proc newSimpleShadowManager*(light: Light;
self.uses_depth = false
when defined(myouDebugShadows):
if debug_mesh != nil:
debug_mesh.destroy()
debug_mesh = self.engine.newMesh(vertex_count = 100, draw_method = Lines)
debug_mesh.materials.add self.engine.newSolidMaterial("green", vec4(0,1,0,1))
debug_mesh.visible = false
@ -105,6 +107,7 @@ proc newSimpleShadowManager*(light: Light;
method destroy*(self: SimpleShadowManager) {.locks:"unknown".} =
if self.material != nil:
self.material.destroy()
debug_mesh.destroy()
when defined(myouDebugShadows):
proc show_mat(mat: Mat4) =

View file

@ -75,96 +75,55 @@ func remove_scale_skew*[T](m: GMat4[T]): GMat4[T] =
result[3,1] = m[3,1]
result[3,2] = m[3,2]
proc remove*[T](s: var seq[T], element: T) =
proc remove*[T](s: var seq[T], element: T): bool {.raises:[],discardable.} =
## Removes a value from a seq if it exists. Returns whether an item was
## removed. Preserves the order of the other elements. If the element is
## repeated, only one instance is removed.
let index = s.find element
if index != -1:
s.delete index
# TODO: option to warn or break when not present
# and a version or an argument to not warn
# Since we only remove an existing element, it should never raise,
# but we have to use try/except to satisfy raises:[]
try: s.delete index
except: discard
return true
proc remove_unordered*[T](s: seq[T], element: T) =
proc remove_unordered*[T](s: seq[T], element: T): bool {.raises:[],discardable.} =
## Removes a value from a seq if it exists. Returns whether an item was
## removed. It's quicker than `remove` by moving the last element to the slot
## of the removed one. If the element is repeated, only one instance is
## removed.
let index = s.find element
if index != -1:
s.del index
# Since we only remove an existing element, it should never raise,
# but we have to use try/except to satisfy raises:[]
try: s.del index
except: discard
return true
func align4*[T: SomeInteger](n: T): T = n + ((4-(n and 3)) and 3)
func align4*[T: SomeInteger](n: T): T =
# Increments the number to the nearest multiplier of 4 if it's not already
# aligned.
n + ((4-(n and 3)) and 3)
func align*[T: SomeInteger](n, align: T): T =
## Increments the number to the nearest multiplier of the `align` parameter,
## if it's not already aligned. `align` must be a power of two.
let mask = align - 1
assert((align and mask) == 0, "align must be a power of two")
return n + ((align-(n and mask)) and mask)
func bytelen*[T](s: seq[T]): int = s.len * sizeof(T)
func bytelen*[T](s: seq[T]): int =
## Provides the size of the seq in bytes.
s.len * sizeof(T)
func get_or_default*[T](s: seq[T], i: int, default: T = T.default): T =
func getOrDefault*[T](s: seq[T], i: int, default: T = T.default): T =
## Returns an element of the seq if the index is within bounds, otherwise it
## returns a default value, optionally given as argument.
if i >= s.low and i <= s.high:
return s[i]
return default
when defined(js):
import jsffi
proc newArrayBufferView(size: uint8): seq[uint8] {.importjs: "(new Uint8Array(#))".}
proc newArrayBufferView(size: uint16): seq[uint16] {.importjs: "(new Uint16Array(#))".}
proc newArrayBufferView(size: uint32): seq[uint32] {.importjs: "(new Uint32Array(#))".}
proc newArrayBufferView(size: int8): seq[int8] {.importjs: "(new Int8Array(#))".}
proc newArrayBufferView(size: int16): seq[int16] {.importjs: "(new Int16Array(#))".}
proc newArrayBufferView(size: int32): seq[int32] {.importjs: "(new Int32Array(#))".}
proc newArrayBufferView(size: float32): seq[float32] {.importjs: "(new Float32Array(#))".}
proc newArrayBufferView(size: float64): seq[float64] {.importjs: "(new Float64Array(#))".}
template makeSeq*[T](size: int): seq[T] =
# incredibly cursed hack, but it works
newArrayBufferView(size.tojs.to(typeof T))
proc as_byte_array*[T](arr: seq[T]): seq[int8] {.importjs: "(new Int8Array((#).buffer))".}
proc as_ubyte_array*[T](arr: seq[T]): seq[uint8] {.importjs: "(new Uint8Array((#).buffer))".}
proc set*[T](arr, arr2: seq[T]) {.importjs: "((#).set(#))"}
template copy_bytes_to*[T](arr, arr2: seq[T]) =
arr2.as_byte_array.set arr.as_byte_array
proc copyWithin*[T](arr: seq[T], target, start, ends: int) {.importjs: "#.copyWithin(#,#,#)".}
else:
template makeSeq*[T](size: int): seq[T] = newSeq[T](size)
template as_byte_array*[T](arr: openArray[T]): ptr UncheckedArray[int8] =
cast[ptr UncheckedArray[int8]](addr arr[0])
template as_ubyte_array*[T](arr: openArray[T]): ptr UncheckedArray[uint8] =
cast[ptr UncheckedArray[uint8]](addr arr[0])
template as_byte_array*[T](arr: ArrRef[T]): ArrRef[int8] =
arr.to(int8)
template as_ubyte_array*[T](arr: ArrRef[T]): ArrRef[uint8] =
arr.to(uint8)
template set*[T](arr, arr2: seq[T]) =
for i,v in arr2:
arr[i] = v
template copy_bytes_to*[T](arr, arr2: seq[T]|ArrRef[T]) =
let src = arr.as_byte_array
let dst = arr2.as_byte_array
for i in 0 .. min(arr.byte_len, arr2.byte_len):
dst[i] = src[i]
# TODO: version of arrays with len, that clamps bounds and can use negative indices
proc copyWithin*[T](arr: ptr UncheckedArray[T], target, start, ends: int) =
let len = ends-start
let target_end = target + len
if start < target and target < ends:
# reverse (#TODO: only the overlapping section?)
var i = ends - 1
var o = target_end - 1
while i != start:
arr[o] = arr[i]
i -= 1; o -= 1
else:
var i = start
var o = target
while i != ends:
arr[o] = arr[i]
i += 1; o += 1
template copyWithin*[T](arr: ArrRef[T], target, start, ends: int) =
copyWithin(cast[ptr UncheckedArray[T]](arr[0].addr), target, start, ends)
template rotate_cw*[T](v: GVec3[T]): GVec3[T] = gvec3[T](v.y, -v.x, v.z)
template rotate_ccw*[T](v: GVec3[T]): GVec3[T] = gvec3[T](-v.y, v.x, v.z)
@ -243,6 +202,13 @@ template high*[T](x: typedesc[GVec2[T]]): GVec2[T] = gvec2[T](T.high,T.high)
template high*[T](x: typedesc[GVec3[T]]): GVec3[T] = gvec3[T](T.high,T.high,T.high)
template high*[T](x: typedesc[GVec4[T]]): GVec4[T] = gvec4[T](T.high,T.high,T.high,T.high)
proc min*[T](v: GVec2[T]): T {.inline.} = min(v.x, v.y)
proc min*[T](v: GVec3[T]): T {.inline.} = min(min(v.x, v.y), v.z)
proc min*[T](v: GVec4[T]): T {.inline.} = min(min(min(v.x, v.y), v.z), v.w)
proc max*[T](v: GVec2[T]): T {.inline.} = max(v.x, v.y)
proc max*[T](v: GVec3[T]): T {.inline.} = max(max(v.x, v.y), v.z)
proc max*[T](v: GVec4[T]): T {.inline.} = max(max(max(v.x, v.y), v.z), v.w)
# bounding box operations
template `&`*[T](a, b: (GVec3[T], GVec3[T])): (GVec3[T], GVec3[T]) =