234 lines
8.8 KiB
Nim
234 lines
8.8 KiB
Nim
# The contents of this file are subject to the Common Public Attribution License
|
|
# Version 1.0 (the “License”); you may not use this file except in compliance
|
|
# with the License. You may obtain a copy of the License at
|
|
# https://myou.dev/licenses/LICENSE-CPAL. The License is based on the Mozilla
|
|
# Public License Version 1.1 but Sections 14 and 15 have been added to cover use
|
|
# of software over a computer network and provide for limited attribution for
|
|
# the Original Developer. In addition, Exhibit A has been modified to be
|
|
# consistent with Exhibit B.
|
|
#
|
|
# Software distributed under the License is distributed on an “AS IS” basis,
|
|
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
|
|
# the specific language governing rights and limitations under the License.
|
|
#
|
|
# The Original Code is Myou Engine.
|
|
#
|
|
# the Original Developer is the Initial Developer.
|
|
#
|
|
# The Initial Developer of the Original Code is the Myou Engine developers.
|
|
# All portions of the code written by the Myou Engine developers are Copyright
|
|
# (c) 2024. All Rights Reserved.
|
|
#
|
|
# Alternatively, the contents of this file may be used under the terms of the
|
|
# GNU Affero General Public License version 3 (the [AGPL-3] License), in which
|
|
# case the provisions of [AGPL-3] License are applicable instead of those above.
|
|
#
|
|
# If you wish to allow use of your version of this file only under the terms of
|
|
# the [AGPL-3] License and not to allow others to use your version of this file
|
|
# under the CPAL, indicate your decision by deleting the provisions above and
|
|
# replace them with the notice and other provisions required by the [AGPL-3]
|
|
# License. If you do not delete the provisions above, a recipient may use your
|
|
# version of this file under either the CPAL or the [AGPL-3] License.
|
|
|
|
import ./platform/gl
|
|
import vmath except Quat
|
|
import std/math
|
|
import std/algorithm
|
|
import arr_ref
|
|
|
|
template to_tuple*[T](v: GVec2[T]): (T, T) =
|
|
(v.x, v.y)
|
|
|
|
template to_tuple*[T](v: GVec3[T]): (T, T, T) =
|
|
(v.x, v.y, v.z)
|
|
|
|
template to_tuple*[T](v: GVec4[T]): (T, T, T, T) =
|
|
(v.x, v.y, v.z, v.w)
|
|
|
|
## Converts a 4x4 matrix to a 3x3 rotation matrix
|
|
## ignoring any scale and skew.
|
|
## If it's not orthogonal, the Z axis is preserved
|
|
## and the other two are made orthogonal.
|
|
func to_mat3_rotation*[T](m: GMat4[T]): GMat3[T] =
|
|
var x = m[0].xyz.normalize
|
|
var y = m[1].xyz.normalize
|
|
var z = m[2].xyz.normalize
|
|
# This favours the Z axis to preserve
|
|
# the direction of cameras and lights
|
|
x = cross(y,z)
|
|
y = cross(z,x)
|
|
return mat3(x,y,z)
|
|
|
|
func to_normal_matrix*[T](m: GMat4[T]): GMat3[T] =
|
|
# TODO: test: is it inverse.transpose or transpose.inverse?
|
|
m.to_mat3.inverse.transpose
|
|
|
|
func to_mat3*[T](m: GMat4[T]): GMat3[T] =
|
|
return mat3(m[0].xyz, m[1].xyz, m[2].xyz)
|
|
|
|
func to_mat4*[T](m: GMat3[T]): GMat4[T] =
|
|
return mat4(m[0].vec4, m[1].vec4, m[2].vec4, vec4(0,0,0,1))
|
|
|
|
func remove_scale_skew*[T](m: GMat4[T]): GMat4[T] =
|
|
result = m.to_mat3_rotation.to_mat4
|
|
result[3,0] = m[3,0]
|
|
result[3,1] = m[3,1]
|
|
result[3,2] = m[3,2]
|
|
|
|
proc remove*[T](s: var seq[T], element: T) =
|
|
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
|
|
|
|
proc remove_unordered*[T](s: seq[T], element: T) =
|
|
let index = s.find element
|
|
if index != -1:
|
|
s.del index
|
|
|
|
func align4*[T: SomeInteger](n: T): T = n + ((4-(n and 3)) and 3)
|
|
|
|
func align*[T: SomeInteger](n, align: T): T =
|
|
let mask = align - 1
|
|
assert((align and mask) == 0, "align must be a power of two")
|
|
return n + ((4-(n and mask)) and mask)
|
|
|
|
func bytelen*[T](s: seq[T]): int = s.len * sizeof(T)
|
|
|
|
func get_or_default*[T](s: seq[T], i: int, default: T = T.default): T =
|
|
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)
|
|
|
|
|
|
template PGLfloat*[T](v: T): ptr GLfloat =
|
|
cast[ptr GLfloat](unsafeAddr v)
|
|
|
|
template get_ptr_len*[T](arr: seq[T]): (ptr UncheckedArray[T], int) =
|
|
if arr.len != 0:
|
|
(cast[ptr UncheckedArray[T]](addr arr[0]), arr.len)
|
|
else:
|
|
(nil, 0)
|
|
|
|
|
|
template error_enum_to_string*(e: GLenum): string =
|
|
case e:
|
|
of GL_NO_ERROR: "GL_NO_ERROR"
|
|
of GL_INVALID_ENUM: "GL_INVALID_ENUM"
|
|
of GL_INVALID_FRAMEBUFFER_OPERATION: "GL_INVALID_FRAMEBUFFER_OPERATION"
|
|
of GL_INVALID_INDEX: "GL_INVALID_INDEX"
|
|
of GL_INVALID_OPERATION: "GL_INVALID_OPERATION"
|
|
of GL_INVALID_VALUE: "GL_INVALID_VALUE"
|
|
else: $e
|
|
|
|
proc fibonacci_sphere*(samples: int): seq[Vec3] =
|
|
const phi: float32 = PI * (sqrt(5'f32) - 1) # golden angle in radians
|
|
for i in 0 ..< samples:
|
|
let y = (1 - (i / (samples - 1)) * 2) * (1 - 0.5'f32/samples.float32)
|
|
let radius = sqrt(1 - y * y) # radius at y
|
|
let theta = phi * i.float32 # golden angle increment
|
|
let x = cos(theta) * radius
|
|
let z = sin(theta) * radius
|
|
result.add vec3(x, y, z)
|
|
|
|
proc `|=`*(a: var GLbitfield, b: GLbitfield) =
|
|
a = (a.GLuint or b.GLuint).GLbitfield
|
|
|
|
when defined(opengl_es):
|
|
proc `not`*(x: GLbitfield): GLbitfield = (not x.GLuint).GLbitfield
|
|
|
|
proc `and`*(a: GLbitfield, b: GLbitfield): GLbitfield =
|
|
(a.GLuint and b.GLuint).GLbitfield
|
|
|
|
proc `$`*(x: GLenum): string = $(x.uint32)
|
|
|
|
|
|
func get_culling_planes*(mat: Mat4): array[6, Vec4] =
|
|
# Gribb/Hartmann method
|
|
[
|
|
mat[3] + mat[0], # left
|
|
mat[3] - mat[0], # right
|
|
mat[3] + mat[1], # bottom
|
|
mat[3] - mat[1], # top
|
|
mat[3] + mat[2], # near
|
|
mat[3] - mat[2], # far
|
|
]
|
|
|
|
template staticOrDebugRead*(path: string): string =
|
|
when not defined(release) and not
|
|
(defined(ios) or defined(android) or defined(emscripten)):
|
|
const dir = currentSourcePath.rsplit('/',1)[0] & '/'
|
|
readFile dir & path
|
|
else:
|
|
staticRead path
|
|
|
|
template nonNil*(x: untyped): bool = x != nil
|