Last active
August 22, 2020 20:14
-
-
Save zacharycarter/74ffbe072fb6cbc77e535d5be3b0a141 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type | |
Handness = enum | |
hLeft, | |
hRight | |
NearFar = enum | |
nfDefault, | |
nfReverse | |
Vec3 = object | |
x: float32 | |
y: float32 | |
z: float32 | |
Plane = object | |
normal: Vec3 | |
dist: float32 | |
Quaternion = object | |
x: float32 | |
y: float32 | |
z: float32 | |
w: float32 | |
proc bitsToFloat(a: uint32): float32 = | |
result = cast[float32](a) | |
proc floatToBits(a: float32): uint32 = | |
result = cast[uint32](a) | |
const | |
kPi: float32 = 3.1415926535897932384626433832795'f32 | |
kPi2: float32 = 6.2831853071795864769252867665590'f32 | |
kInvPi: float32 = 1.0/kPi | |
kPiHalf: float32 = 1.5707963267948966192313216916398'f32 | |
kPiQuarter: float32 = 0.7853981633974483096156608458199'f32 | |
kSqrt2: float32 = 1.4142135623730950488016887242097'f32 | |
kLogNat10: float32 = 2.3025850929940456840179914546844'f32 | |
kInvLogNat2: float32 = 1.4426950408889634073599246810019'f32 | |
kLogNat2Hi: float32 = 0.6931471805599453094172321214582'f32 | |
kLogNat2Lo: float32 = 1.90821492927058770002e-10'f32 | |
kE: float32 = 2.7182818284590452353602874713527'f32 | |
kNearZero: float32 = 1.0'f32/float32(1 shl 28) | |
kFloatMin: float32 = 1.175494e-38'f32 | |
kFloatMax: float32 = 3.402823e+38'f32 | |
kSinC2: float32 = -0.16666667163372039794921875'f32 | |
kSinC4: float32 = 8.333347737789154052734375e-3'f32 | |
kSinC6: float32 = -1.9842604524455964565277099609375e-4'f32 | |
kSinC8: float32 = 2.760012648650445044040679931640625e-6'f32 | |
kSinC10: float32 = -2.50293279435709337121807038784027099609375e-8'f32 | |
kCosC2: float32 = -0.5'f32 | |
kCosC4: float32 = 4.166664183139801025390625e-2'f32 | |
kCosC6: float32 = -1.388833043165504932403564453125e-3'f32 | |
kCosC8: float32 = 2.47562347794882953166961669921875e-5'f32 | |
kCosC10: float32 = -2.59630184018533327616751194000244140625e-7'f32 | |
kAcosC0: float32 = 1.5707288'f32 | |
kAcosC1: float32 = -0.2121144'f32 | |
kAcosC2: float32 = 0.0742610'f32 | |
kAcosC3: float32 = -0.0187293'f32 | |
kExpC0: float32 = 1.66666666666666019037e-01'f32 | |
kExpC1: float32 = -2.77777777770155933842e-03'f32 | |
kExpC2: float32 = 6.61375632143793436117e-05'f32 | |
kExpC3: float32 = -1.65339022054652515390e-06'f32 | |
kExpC4: float32 = 4.13813679705723846039e-08'f32 | |
kLogC0: float32 = 6.666666666666735130e-01'f32 | |
kLogC1: float32 = 3.999999999940941908e-01'f32 | |
kLogC2: float32 = 2.857142874366239149e-01'f32 | |
kLogC3: float32 = 2.222219843214978396e-01'f32 | |
kLogC4: float32 = 1.818357216161805012e-01'f32 | |
kLogC5: float32 = 1.531383769920937332e-01'f32 | |
kLogC6: float32 = 1.479819860511658591e-01'f32 | |
kAtan2C0: float32 = -0.013480470'f32 | |
kAtan2C1: float32 = 0.057477314'f32 | |
kAtan2C2: float32 = -0.121239071'f32 | |
kAtan2C3: float32 = 0.195635925'f32 | |
kAtan2C4: float32 = -0.332994597'f32 | |
kAtan2C5: float32 = 0.999995630'f32 | |
kInfinity: float32 = bitsToFloat(uint32(0x7f800000)) | |
proc uint32And(a, b: uint32): uint32 = | |
result = a and b | |
proc uint32Or(a, b: uint32): uint32 = | |
result = a or b | |
proc uint32Sra(a: uint32, sa: int32): uint32 = | |
result = uint32(int32(a) shr sa) | |
proc uint32IAdd(a, b: uint32): uint32 = | |
result = uint32(int32(a) + int32(b)) | |
proc uint32Sll(a: uint32, sa: int32): uint32 = | |
result = a shl sa | |
proc uint32Srl(a: uint32, sa: int32): uint32 = | |
result = a shr sa | |
proc toRad(deg: float32): float32 = | |
result = deg * kPi / 180.0'f32 | |
proc abs(a: float32): float32 = | |
result = if a < 0.0'f32: -a else: a | |
proc square(a: float32): float32 = | |
result = a * a | |
proc mad(a, b, c: float32): float32 = | |
result = a * b + c | |
proc trunc(a: float32): float32 = | |
result = float32(int(a)) | |
proc fract(a: float32): float32 = | |
result = a - trunc(a) | |
proc floor(a: float32): float32 = | |
if (a < 0.0): | |
let fr = fract(-a) | |
result = -a - fr | |
return -(if 0.0 != fr: result + 1.0 else: result) | |
result = a - fract(a) | |
proc round(f: float32): float32 = | |
result = floor(f + 0.5'f32) | |
proc sign(a: float32): float32 = | |
result = if a < 0.0'f32: -1.0'f32 else: 1.0'f32 | |
proc ldexp(a: float32, b: int32): float32 = | |
let | |
ftob: uint32 = floatToBits(a) | |
masked: uint32 = uint32And(ftob, uint32(0xff800000)) | |
expsign0: uint32 = uint32Sra(masked, 23) | |
tmp: uint32 = uint32Iadd(expsign0, uint32(b)) | |
expsign1: uint32 = uint32Sll(tmp, 23) | |
mantissa: uint32 = uint32And(ftob, uint32(0x007fffff)) | |
bits: uint32 = uint32Or(mantissa, expsign1) | |
result = bitsToFloat(bits) | |
proc exp(a: float32): float32 = | |
if abs(a) <= kNearZero: | |
return a + 1.0'f32 | |
let | |
kk: float32 = round(a*kInvLogNat2) | |
hi: float32 = a - kk*kLogNat2Hi | |
lo: float32 = kk*kLogNat2Lo | |
hml: float32 = hi - lo | |
hmlsq: float32 = square(hml) | |
tmp0: float32 = mad(kExpC4, hmlsq, kExpC3) | |
tmp1: float32 = mad(tmp0, hmlsq, kExpC2) | |
tmp2: float32 = mad(tmp1, hmlsq, kExpC1) | |
tmp3: float32 = mad(tmp2, hmlsq, kExpC0) | |
tmp4: float32 = hml - hmlsq * tmp3 | |
tmp5: float32 = hml*tmp4/(2.0-tmp4) | |
tmp6: float32 = 1.0 - ((lo - tmp5) - hi) | |
result = ldexp(tmp6, int32(kk)) | |
proc frexp(a: float32, outExp: var int32): float32 = | |
let | |
ftob: uint32 = floatToBits(a) | |
masked0: uint32 = uint32And(ftob, uint32(0x7f800000)) | |
exp0: uint32 = uint32Srl(masked0, 23) | |
masked1: uint32 = uint32And(ftob, uint32(0x807fffff)) | |
bits: uint32 = uint32Or(masked1, uint32(0x3f000000)) | |
outExp = int32(exp0 - 0x7e) | |
result = bitsToFloat(bits) | |
proc log(a: float32): float32 = | |
var | |
exp: int32 | |
ff: float32 = frexp(a, exp) | |
if ff < kSqrt2*0.5'f32: | |
ff *= 2.0'f32 | |
dec(exp) | |
ff -= 1.0'f32 | |
let | |
kk: float32 = float32(exp) | |
hi: float32 = kk*kLogNat2Hi | |
lo: float32 = kk*kLogNat2Lo | |
ss: float32 = ff / (2.0'f32 + ff) | |
s2: float32 = square(ss) | |
s4: float32 = square(s2) | |
tmp0: float32 = mad(kLogC6, s4, kLogC4) | |
tmp1: float32 = mad(tmp0, s4, kLogC2) | |
tmp2: float32 = mad(tmp1, s4, kLogC0) | |
t1: float32 = s2*tmp2 | |
tmp3: float32 = mad(kLogC5, s4, kLogC3) | |
tmp4: float32 = mad(tmp3, s4, kLogC1) | |
t2: float32 = s4*tmp4 | |
t12: float32 = t1 + t2 | |
hfsq: float32 = 0.5'f32*square(ff) | |
result = hi - ((hfsq - (ss*(hfsq+t12) + lo)) - ff) | |
proc pow(a, b: float32): float32 = | |
result = exp(b * log(a)) | |
proc rSqrt(a: float32): float32 = | |
result = pow(a, -0.5'f32) | |
proc sqrt(a: float32): float32 = | |
if a < kNearZero: | |
result = 0.0'f32 | |
else: | |
result = 1.0'f32/rSqrt(a) | |
proc cos(a: float32): float32 = | |
let scaled = a * 2.0'f32*kInvPi | |
let real = floor(scaled) | |
let xx = a - real * kPiHalf | |
let bits = int32(real) and 3 | |
var c0, c2, c4, c6, c8, c10: float32 | |
if bits == 0 or bits == 2: | |
c0 = 1.0'f32 | |
c2 = kCosC2 | |
c4 = kCosC4 | |
c6 = kCosC6 | |
c8 = kCosC8 | |
c10 = kCosC10 | |
else: | |
c0 = xx | |
c2 = kSinC2 | |
c4 = kSinC4 | |
c6 = kSinC6 | |
c8 = kSinC8 | |
c10 = kSinC10 | |
let | |
xsq = square(xx) | |
tmp0 = mad(c10, xsq, c8) | |
tmp1 = mad(tmp0, xsq, c6) | |
tmp2 = mad(tmp1, xsq, c4) | |
tmp3 = mad(tmp2, xsq, c2) | |
tmp4 = mad(tmp3, xsq, 1.0'f32) | |
result = tmp4 * c0 | |
result = if bits == 1 or bits == 2: -result else: result | |
proc sin(a: float32): float32 = | |
result = cos(a - kPiHalf) | |
proc tan(a: float32): float32 = | |
result = sin(a) / cos(a) | |
proc acos(a: float32): float32 = | |
let | |
absa = abs(a) | |
tmp0 = mad(kAcosC3, absa, kAcosC2) | |
tmp1 = mad(tmp0, absa, kAcosC1) | |
tmp2 = mad(tmp1, absa, kAcosC0) | |
tmp3 = tmp2 * sqrt(1.0'f32 - absa) | |
negate = float32(a < 0.0'f32) | |
tmp4 = tmp3 - 2.0'f32*negate*tmp3 | |
result = negate*kPi + tmp4 | |
proc atan2(y, x: float32): float32 = | |
let | |
ax: float32 = abs(x) | |
ay: float32 = abs(y) | |
maxaxy: float32 = max(ax, ay) | |
minaxy: float32 = min(ax, ay) | |
if maxaxy == 0.0'f32: | |
return 0.0'f32*sign(y) | |
let | |
mxy: float32 = minaxy / maxaxy | |
mxysq: float32 = square(mxy) | |
tmp0: float32 = mad(kAtan2C0, mxysq, kAtan2C1) | |
tmp1: float32 = mad(tmp0, mxysq, kAtan2C2) | |
tmp2: float32 = mad(tmp1, mxysq, kAtan2C3) | |
tmp3: float32 = mad(tmp2, mxysq, kAtan2C4) | |
tmp4: float32 = mad(tmp3, mxysq, kAtan2C5) | |
tmp5: float32 = tmp4 * mxy | |
tmp6: float32 = if ay > ax: kPiHalf - tmp5 else: tmp5 | |
tmp7: float32 = if x < 0.0'f32: kPi - tmp6 else: tmp6 | |
result = sign(y)*tmp7 | |
proc mul(a: Vec3, b: float32): Vec3 = | |
result.x = a.x * b | |
result.y = a.y * b | |
result.z = a.z * b | |
proc dot(a, b: Vec3): float32 = | |
result = a.x*b.x + a.y*b.y + a.z*b.z | |
proc length(a: Vec3): float32 = | |
result = sqrt(dot(a, a)) | |
proc sub(a, b: Vec3): Vec3 = | |
result.x = a.x - b.x | |
result.y = a.y - b.y | |
result.z = a.z - b.z | |
proc normalize(a: Vec3): Vec3 = | |
let invLen: float32 = 1.0'f32/length(a) | |
result = mul(a, invLen) | |
proc cross(a, b: Vec3): Vec3 = | |
result.x = a.y*b.z - a.z*b.y | |
result.y = a.z*b.x - a.x*b.z | |
result.z = a.x*b.y - a.y*b.x | |
proc mtxLookAt(result: var array[16, float32], eye, at, up: Vec3, handness: Handness) = | |
let | |
view: Vec3 = normalize( | |
if handness == hRight: sub(eye, at) else: sub(at, eye) | |
) | |
uxv: Vec3 = cross(up, view) | |
right: Vec3 = normalize(uxv) | |
up: Vec3 = cross(view, right) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = right.x | |
result[1] = up.x | |
result[2] = view.x | |
result[4] = right.y | |
result[5] = up.y | |
result[6] = view.y | |
result[8] = right.z | |
result[9] = up.z | |
result[10] = view.z | |
result[12] = -dot(right, eye) | |
result[13] = -dot(up, eye) | |
result[14] = -dot(view, eye) | |
result[15] = 1.0'f32 | |
proc mtxProjXYWH(result: var array[16, float32], x, y, width, height, near, far: float32, homogeneousNdc: bool, handness: Handness) = | |
let | |
diff: float32 = far - near | |
aa: float32 = if homogeneousNdc: (far+near)/diff else: far/diff | |
bb: float32 = if homogeneousNdc: (2.0'f32*far*near)/diff else: near*aa | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = width | |
result[5] = height | |
result[8] = if handness == hRight: x else: -x | |
result[9] = if handness == hRight: y else: -y | |
result[10] = if handness == hRight: -aa else: aa | |
result[11] = if handness == hRight: -1.0'f32 else: 1.0'f32 | |
result[14] = -bb | |
proc mtxProj(result: var array[16, float32], ut, dt, lt, rt, near, far: float32, homogeneousNdc: bool, handness: Handness) = | |
let | |
invDiffRl: float32 = 1.0'f32/(rt - lt) | |
invDiffUd: float32 = 1.0'f32/(ut - dt) | |
width: float32 = 2.0'f32*near * invDiffRl | |
height: float32 = 2.0'f32*near * invDiffUd | |
xx: float32 = (rt + lt) * invDiffRl | |
yy: float32 = (ut + dt) * invDiffUd | |
mtxProjXYWH(result, xx, yy, width, height, near, far, homogeneousNdc, handness) | |
proc mtxProj(result: var array[16, float32], fov: array[4, float32], near, far: float32, homogeneousNdc: bool, handness: Handness) = | |
mtxProj(result, fov[0], fov[1], fov[2], fov[3], near, far, homogeneousNdc, handness) | |
proc mtxProj(result: var array[16, float32], fovy, aspect, near, far: float32, homogeneousNdc: bool, handness: Handness) = | |
let | |
height: float32 = 1.0'f32/tan(toRad(fovy)*0.5'f32) | |
width: float32 = height * 1.0'f32/aspect | |
mtxProjXYWH(result, 0.0'f32, 0.0'f32, width, height, near, far, homogeneousNdc, handness) | |
proc mtxProjInfXYWH(result: var array[16, float32], x, y, width, height, near: float32, homogeneousNdc: bool, handness: Handness, nearFar: NearFar) = | |
var | |
aa: float32 | |
bb: float32 | |
if nearFar == nfReverse: | |
aa = if homogeneousNdc: -1.0'f32 else: 0.0'f32 | |
bb = if homogeneousNdc: -2.0'f32*near else: -near | |
else: | |
aa = 1.0'f32 | |
bb = if homogeneousNdc: 2.0'f32*near else: near | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = width | |
result[5] = height | |
result[8] = if handness == hRight: x else: -x | |
result[9] = if handness == hRight: y else: -y | |
result[10] = if handness == hRight: -aa else: aa | |
result[11] = if handness == hRight: -1.0'f32 else: 1.0'f32 | |
result[14] = -bb | |
proc mtxProjInf(result: var array[16, float32], ut, dt, lt, rt, near: float32, homogeneousNdc: bool, handness: Handness, nearFar: NearFar) = | |
let | |
invDiffR1: float32 = 1.0'f32/(rt - lt) | |
invDiffUd: float32 = 1.0'f32/(ut - dt) | |
width: float32 = 2.0'f32*near * invDiffR1 | |
height: float32 = 2.0'f32*near * invDiffUd | |
xx: float32 = (rt + lt) * invDiffR1 | |
yy: float32 = (ut + dt) * invDiffUd | |
mtxProjInfXYWH(result, xx, yy, width, height, near, homogeneousNdc, handness, nearFar) | |
proc mtxProjInf(result: var array[16, float32], fov: array[4, float32], near: float32, homogeneousNdc: bool, handness: Handness, nearFar: NearFar) = | |
mtxProjInf(result, fov[0], fov[1], fov[2], fov[3], near, homogeneousNdc, handness, nearFar) | |
proc mtxProjInf(result: var array[16, float32], fovy, aspect, near: float32, homogeneousNdc: bool, handness: Handness, nearFar: NearFar) = | |
let | |
height = 1.0'f32/tan(toRad(fovy)*0.5'f32) | |
width = height * 1.0'f32/aspect | |
mtxProjInfXYWH(result, 0.0'f32, 0.0'f32, width, height, near, homogeneousNdc, handness, nearFar) | |
proc mtxOrtho(result: var array[16, float32], left, right, bottom, top, near, far, offset: float32, homogeneousNdc: bool, handness: Handness) = | |
let | |
aa: float32 = 2.0'f32/(right - left) | |
bb: float32 = 2.0'f32/(top - bottom) | |
cc: float32 = (if homogeneousNdc: 2.0'f32 else: 1.0'f32) / (far - near) | |
dd: float32 = (left + right)/(left - right) | |
ee: float32 = (top + bottom)/(bottom - top) | |
ff: float32 = if homogeneousNdc: (near + far)/(near - far) else: near/(near - far) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = aa | |
result[5] = bb | |
result[10] = if handness == hRight: -cc else: cc | |
result[12] = dd + offset | |
result[13] = ee | |
result[14] = ff | |
result[15] = 1.0'f32 | |
proc mtxRotateX(result: var array[16, float32], ax: float32) = | |
let | |
sx: float32 = sin(ax) | |
cx: float32 = cos(ax) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = 1.0'f32 | |
result[5] = cx | |
result[6] = -sx | |
result[9] = sx | |
result[10] = cx | |
result[15] = 1.0'f32 | |
proc mtxRotateY(result: var array[16, float32], ay: float32) = | |
let | |
sy: float32 = sin(ay) | |
cy: float32 = cos(ay) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = cy | |
result[2] = sy | |
result[5] = 1.0'f32 | |
result[8] = -sy | |
result[10] = cy | |
result[15] = 1.0'f32 | |
proc mtxRotateZ(result: var array[16, float32], az: float32) = | |
let | |
sz = sin(az) | |
cz = cos(az) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = cz | |
result[1] = -sz | |
result[4] = sz | |
result[5] = cz | |
result[10] = 1.0'f32 | |
result[15] = 1.0'f32 | |
proc mtxRotateXY(result: var array[16, float32], ax, ay: float32) = | |
let | |
sx: float32 = sin(ax) | |
cx: float32 = sin(ax) | |
sy: float32 = sin(ay) | |
cy: float32 = cos(ay) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = cy | |
result[2] = sy | |
result[4] = sx*sy | |
result[5] = cx | |
result[6] = -sx*cy | |
result[8] = -cx*sy | |
result[9] = sx | |
result[10] = cx*cy | |
result[15] = 1.0'f32 | |
proc mtxRotateXYZ(result: var array[16, float32], ax, ay, az: float32) = | |
let | |
sx: float32 = sin(ax) | |
cx: float32 = sin(ax) | |
sy: float32 = sin(ay) | |
cy: float32 = cos(ay) | |
sz: float32 = sin(az) | |
cz: float32 = cos(az) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = cy*cz | |
result[1] = -cy*sz | |
result[2] = sy | |
result[4] = cz*sx*sy + cx*sz | |
result[5] = cx*cz - sx*sy*sz | |
result[6] = -cy*sx | |
result[8] = -cx*cz*sy + sx*sz | |
result[9] = cz*sx + cx*sy*sz | |
result[10] = cx*cy | |
result[15] = 1.0'f32 | |
proc mtxRotateZYX(result: var array[16, float32], ax, ay, az: float32) = | |
let | |
sx: float32 = sin(ax) | |
cx: float32 = sin(ax) | |
sy: float32 = sin(ay) | |
cy: float32 = cos(ay) | |
sz: float32 = sin(az) | |
cz: float32 = cos(az) | |
zeroMem(addr result, sizeof(float32)*16) | |
result[0] = cy*cz | |
result[1] = cz*sx*sy-cx*sz | |
result[2] = cx*cz*sy+sx*sz | |
result[4] = cy*sz | |
result[5] = cx*cz + sx*sy*sz | |
result[6] = -cz*sx + cx*sy*sz | |
result[8] = -sy | |
result[9] = cy*sx | |
result[10] = cx*cy | |
result[15] = 1.0'f32 | |
proc mtxSRT(result: var array[16, float32], sx, sy, sz, ax, ay, az, tx, ty, tz: float32) = | |
let | |
sx: float32 = sin(ax) | |
cx: float32 = sin(ax) | |
sy: float32 = sin(ay) | |
cy: float32 = cos(ay) | |
sz: float32 = sin(az) | |
cz: float32 = cos(az) | |
sxsz: float32 = sx*sz | |
cycz: float32 = cy*cz | |
result[0] = sx * (cycz - sxsz*sy) | |
result[1] = sx * -cx*sz | |
result[2] = sx * (cz*sy + cy*sxsz) | |
result[3] = 0.0'f32 | |
result[4] = sy * (cz*sx*sy + cy*sz) | |
result[5] = sy * cx*cz | |
result[6] = sy * (sy*sz - cycz*sx) | |
result[7] = 0.0'f32 | |
result[8] = sz * -cx*sy | |
result[9] = sz * sx | |
result[10] = sz * cx*cy | |
result[11] = 0.0'f32 | |
result[12] = tx | |
result[13] = ty | |
result[14] = tz | |
result[15] = 1.0'f32 | |
proc mtx3Inverse(result: var array[9, float32], a: array[9, float32]) = | |
let | |
xx: float32 = a[0] | |
xy: float32 = a[1] | |
xz: float32 = a[2] | |
yx: float32 = a[3] | |
yy: float32 = a[4] | |
yz: float32 = a[5] | |
zx: float32 = a[6] | |
zy: float32 = a[7] | |
zz: float32 = a[8] | |
var det: float32 = 0.0'f32 | |
det += xx * (yy*zz - yz*zy) | |
det -= xy * (yx*zz - yz*zx) | |
det += xz * (yx*zy - yy*zx) | |
let invDet = 1.0'f32/det | |
result[0] = +(yy*zz - yz*zy) * invDet | |
result[1] = -(xy*zz - xz*zy) * invDet | |
result[2] = +(xy*yz - xz*yy) * invDet | |
result[3] = -(yx*zz - yz*zx) * invDet | |
result[4] = +(xx*zz - xz*zx) * invDet | |
result[5] = -(xx*yz - xz*yx) * invDet | |
result[6] = +(yx*zy - yy*zx) * invDet | |
result[7] = -(xx*zy - xy*zx) * invDet | |
result[8] = +(xx*yy - xy*yx) * invDet |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment