Matrix (in)sanity (#19366)

Ported the various matrix procs and defines from TG.
Updated our snowflake procs with the Bay's current version.
Refactor of our orbit proc to align with the updated procs, tesla energy
ball too.
This commit is contained in:
Fluffy
2024-06-09 17:07:58 +00:00
committed by GitHub
parent 9acce89e2a
commit c1a47cf20b
6 changed files with 366 additions and 67 deletions
+27
View File
@@ -0,0 +1,27 @@
/// Helper macro for creating a matrix at the given offsets.
/// Works at compile time.
#define TRANSLATE_MATRIX(offset_x, offset_y) matrix(1, 0, (offset_x), 0, 1, (offset_y))
/// The color matrix of an image which colors haven't been altered. Does nothing.
#define COLOR_MATRIX_IDENTITY list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0)
/// Color inversion
#define COLOR_MATRIX_INVERT list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0)
///Sepiatone
#define COLOR_MATRIX_SEPIATONE list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0)
///Grayscale
#define COLOR_MATRIX_GRAYSCALE list(0.33,0.33,0.33,0, 0.59,0.59,0.59,0, 0.11,0.11,0.11,0, 0,0,0,1, 0,0,0,0)
///Polaroid colors
#define COLOR_MATRIX_POLAROID list(1.438,-0.062,-0.062,0, -0.122,1.378,-0.122,0, -0.016,-0.016,1.483,0, 0,0,0,1, 0,0,0,0)
/// Converts reds to blue, green to red and blue to green.
#define COLOR_MATRIX_BRG list(0,0,1,0, 0,1,0,0, 1,0,0,0, 0,0,0,1, 0,0,0,0)
/// Black & White
#define COLOR_MATRIX_BLACK_WHITE list(1.5,1.5,1.5,0, 1.5,1.5,1.5,0, 1.5,1.5,1.5,0, 0,0,0,1, -1,-1,-1,0)
/**
* Adds/subtracts overall lightness
* 0 is identity, 1 makes everything white, -1 makes everything black
*/
#define COLOR_MATRIX_LIGHTNESS(power) list(1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1, power,power,power,0)
/**
* Changes distance colors have from rgb(127,127,127) grey
* 1 is identity. 0 makes everything grey >1 blows out colors and greys
*/
#define COLOR_MATRIX_CONTRAST(val) list(val,0,0,0, 0,val,0,0, 0,0,val,0, 0,0,0,1, (1-val)*0.5,(1-val)*0.5,(1-val)*0.5,0)
+273 -57
View File
@@ -1,56 +1,275 @@
//Luma coefficients suggested for HDTVs. If you change these, make sure they add up to 1.
#define LUMA_R 0.213
#define LUMA_G 0.715
#define LUMA_B 0.072
/// Datum which stores information about a matrix decomposed with decompose().
/datum/decompose_matrix
///?
var/scale_x = 1
///?
var/scale_y = 1
///?
var/rotation = 0
///?
var/shift_x = 0
///?
var/shift_y = 0
/// Decomposes a matrix into scale, shift and rotation.
///
/// If other operations were applied on the matrix, such as shearing, the result
/// will not be precise.
///
/// Negative scales are now supported. =)
/matrix/proc/decompose()
var/datum/decompose_matrix/decompose_matrix = new
. = decompose_matrix
var/flip_sign = (a*e - b*d < 0)? -1 : 1 // Det < 0 => only 1 axis is flipped - start doing some sign flipping
// If both axis are flipped, nothing bad happens and Det >= 0, it just treats it like a 180° rotation
// If only 1 axis is flipped, we need to flip one direction - in this case X, so we flip a, b and the x scaling
decompose_matrix.scale_x = sqrt(a * a + d * d) * flip_sign
decompose_matrix.scale_y = sqrt(b * b + e * e)
decompose_matrix.shift_x = c
decompose_matrix.shift_y = f
if(!decompose_matrix.scale_x || !decompose_matrix.scale_y)
return
// If only translated, scaled and rotated, a/xs == e/ys and -d/xs == b/xy
var/cossine = (a/decompose_matrix.scale_x + e/decompose_matrix.scale_y) / 2
var/sine = (b/decompose_matrix.scale_y - d/decompose_matrix.scale_x) / 2 * flip_sign
decompose_matrix.rotation = arctan(cossine, sine) * flip_sign
/matrix/proc/TurnTo(old_angle, new_angle)
. = new_angle - old_angle
Turn(.) //BYOND handles cases such as -270, 360, 540 etc. DOES NOT HANDLE 180 TURNS WELL, THEY TWEEN AND LOOK LIKE SHIT
/**
* Shear the transform on either or both axes.
* * x - X axis shearing
* * y - Y axis shearing
*/
/matrix/proc/Shear(x, y)
return Multiply(matrix(1, x, 0, y, 1, 0))
/atom/proc/SpinAnimation(speed = 10, loops = -1, clockwise = 1, segments = 3)
if(!segments)
return
var/segment = 360/segments
if(!clockwise)
segment = -segment
var/list/matrices = list()
for(var/i in 1 to segments-1)
var/matrix/M = matrix(transform)
M.Turn(segment*i)
matrices += M
var/matrix/last = matrix(transform)
matrices += last
//Dumps the matrix data in format a-f
/matrix/proc/tolist()
. = list()
. += a
. += b
. += c
. += d
. += e
. += f
speed /= segments
//Dumps the matrix data in a matrix-grid format
/*
a d 0
b e 0
c f 1
*/
/matrix/proc/togrid()
. = list()
. += a
. += d
. += 0
. += b
. += e
. += 0
. += c
. += f
. += 1
animate(src, transform = matrices[1], time = speed, loops)
for(var/i in 2 to segments) //2 because 1 is covered above
animate(transform = matrices[i], time = speed)
//doesn't have an object argument because this is "Stacking" with the animate call above
//3 billion% intentional
///The X pixel offset of this matrix
/matrix/proc/get_x_shift()
. = c
/atom/proc/shake_animation(var/intensity = 8)
///The Y pixel offset of this matrix
/matrix/proc/get_y_shift()
. = f
/////////////////////
// COLOUR MATRICES //
/////////////////////
/* Documenting a couple of potentially useful color matrices here to inspire ideas
// Greyscale - indentical to saturation @ 0
list(LUMA_R,LUMA_R,LUMA_R,0, LUMA_G,LUMA_G,LUMA_G,0, LUMA_B,LUMA_B,LUMA_B,0, 0,0,0,1, 0,0,0,0)
// Color inversion
list(-1,0,0,0, 0,-1,0,0, 0,0,-1,0, 0,0,0,1, 1,1,1,0)
// Sepiatone
list(0.393,0.349,0.272,0, 0.769,0.686,0.534,0, 0.189,0.168,0.131,0, 0,0,0,1, 0,0,0,0)
*/
//Changes distance hues have from grey while maintaining the overall lightness. Greys are unaffected.
//1 is identity, 0 is greyscale, >1 oversaturates colors
/proc/color_matrix_saturation(value)
var/inv = 1 - value
var/R = round(LUMA_R * inv, 0.001)
var/G = round(LUMA_G * inv, 0.001)
var/B = round(LUMA_B * inv, 0.001)
return list(R + value,R,R,0, G,G + value,G,0, B,B,B + value,0, 0,0,0,1, 0,0,0,0)
//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting greys
//0 is identity, 120 moves reds to greens, 240 moves reds to blues
/proc/color_matrix_rotate_hue(angle)
var/sin = sin(angle)
var/cos = cos(angle)
var/cos_inv_third = 0.333*(1-cos)
var/sqrt3_sin = sqrt(3)*sin
return list(
round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), 0,
round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), round(cos_inv_third+sqrt3_sin, 0.001), 0,
round(cos_inv_third+sqrt3_sin, 0.001), round(cos_inv_third-sqrt3_sin, 0.001), round(cos+cos_inv_third, 0.001), 0,
0,0,0,1,
0,0,0,0)
//These next three rotate values about one axis only
//x is the red axis, y is the green axis, z is the blue axis.
/proc/color_matrix_rotate_x(angle)
var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001)
return list(1,0,0,0, 0,cosval,sinval,0, 0,-sinval,cosval,0, 0,0,0,1, 0,0,0,0)
/proc/color_matrix_rotate_y(angle)
var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001)
return list(cosval,0,-sinval,0, 0,1,0,0, sinval,0,cosval,0, 0,0,0,1, 0,0,0,0)
/proc/color_matrix_rotate_z(angle)
var/sinval = round(sin(angle), 0.001); var/cosval = round(cos(angle), 0.001)
return list(cosval,sinval,0,0, -sinval,cosval,0,0, 0,0,1,0, 0,0,0,1, 0,0,0,0)
//Returns a matrix addition of A with B
/proc/color_matrix_add(list/A, list/B)
if(!istype(A) || !istype(B))
return COLOR_MATRIX_IDENTITY
if(A.len != 20 || B.len != 20)
return COLOR_MATRIX_IDENTITY
var/list/output = list()
output.len = 20
for(var/value in 1 to 20)
output[value] = A[value] + B[value]
return output
//Returns a matrix multiplication of A with B
/proc/color_matrix_multiply(list/A, list/B)
if(!istype(A) || !istype(B))
return COLOR_MATRIX_IDENTITY
if(A.len != 20 || B.len != 20)
return COLOR_MATRIX_IDENTITY
var/list/output = list()
output.len = 20
var/x = 1
var/y = 1
var/offset = 0
for(y in 1 to 5)
offset = (y-1)*4
for(x in 1 to 4)
output[offset+x] = round(A[offset+1]*B[x] + A[offset+2]*B[x+4] + A[offset+3]*B[x+8] + A[offset+4]*B[x+12]+(y == 5?B[x+16]:0), 0.001)
return output
/**
* Converts RGB shorthands into RGBA matrices complete of constants rows (ergo a 20 keys list in byond).
* if return_identity_on_fail is true, stack_trace is called instead of CRASH, and an identity is returned.
*/
/proc/color_to_full_rgba_matrix(color, return_identity_on_fail = TRUE)
if(!color)
return COLOR_MATRIX_IDENTITY
if(istext(color))
var/list/L = rgb2num(color)
if(!L)
var/message = "Invalid/unsupported color ([color]) argument in color_to_full_rgba_matrix()"
if(return_identity_on_fail)
stack_trace(message)
return COLOR_MATRIX_IDENTITY
CRASH(message)
return list(L[1]/255,0,0,0, 0,L[2]/255,0,0, 0,0,L[3]/255,0, 0,0,0,L.len>3?L[4]/255:1, 0,0,0,0)
if(!islist(color)) //invalid format
CRASH("Invalid/unsupported color ([color]) argument in color_to_full_rgba_matrix()")
var/list/L = color
switch(L.len)
if(3 to 5) // row-by-row hexadecimals
. = list()
for(var/a in 1 to L.len)
var/list/rgb = rgb2num(L[a])
for(var/b in rgb)
. += b/255
if(length(rgb) % 4) // RGB has no alpha instruction
. += a != 4 ? 0 : 1
if(L.len < 4) //missing both alphas and constants rows
. += list(0,0,0,1, 0,0,0,0)
else if(L.len < 5) //missing constants row
. += list(0,0,0,0)
if(9 to 12) //RGB
. = list(L[1],L[2],L[3],0, L[4],L[5],L[6],0, L[7],L[8],L[9],0, 0,0,0,1)
for(var/b in 1 to 3) //missing constants row
. += L.len < 9+b ? 0 : L[9+b]
. += 0
if(16 to 20) // RGBA
. = L.Copy()
if(L.len < 20) //missing constants row
for(var/b in 1 to 20-L.len)
. += 0
else
var/message = "Invalid/unsupported color (list of length [L.len]) argument in color_to_full_rgba_matrix()"
if(return_identity_on_fail)
stack_trace(message)
return COLOR_MATRIX_IDENTITY
CRASH(message)
/*############################
AURORA SNOWFLAKE SECTION
(Most are from Bay)
############################*/
/**
* Performs a spin/rotation animation on the atom's sprite.
*
* **Parameters**:
* - `speed` (int) - How quickly the atom should rotate.
* - `loops` (int) - How many times the spin animation should occur. Set to `-1` for infinite looping.
*/
/atom/proc/SpinAnimation(speed = 10, loops = -1)
var/matrix/m120 = matrix(transform).Update(rotation = 120)
var/matrix/m240 = matrix(transform).Update(rotation = 240)
var/matrix/m360 = matrix(transform).Update(rotation = 360)
animate(src, transform = m120, time = speed / 3, loops)
animate(transform = m240, time = speed / 3)
animate(transform = m360, time = speed / 3)
/**
* Performs a shaking animation on the atom's sprite.
*
* **Parameters**:
* - `intensity` integer - The intensity of the shaking.
*/
/atom/proc/shake_animation(intensity = 8)
var/init_px = pixel_x
var/shake_dir = pick(-1, 1)
animate(src, transform=turn(matrix(), intensity*shake_dir), pixel_x=init_px + 2*shake_dir, time=1)
animate(
src,
transform = matrix().Update(rotation = intensity * shake_dir),
pixel_x = init_px + 2 * shake_dir,
time = 1
)
animate(transform=null, pixel_x=init_px, time=6, easing=ELASTIC_EASING)
return intensity
// Color matrices:
//Luma coefficients suggested for HDTVs. If you change these, make sure they add up to 1.
#define LUMR 0.2126
#define LUMG 0.7152
#define LUMB 0.0722
//Still need color matrix addition, negation, and multiplication.
//Returns an identity color matrix which does nothing
///Returns an identity color matrix which does nothing
/proc/color_identity()
RETURN_TYPE(/list)
return list(1,0,0, 0,1,0, 0,0,1)
//Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites
//TODO: Need a version that only affects one color (ie shift red to blue but leave greens and blues alone)
///Moves all colors angle degrees around the color wheel while maintaining intensity of the color and not affecting whites
/proc/color_rotation(angle)
RETURN_TYPE(/list)
if(angle == 0)
return color_identity()
angle = Clamp(angle, -180, 180)
angle = clamp(angle, -180, 180)
var/cos = cos(angle)
var/sin = sin(angle)
@@ -58,19 +277,12 @@
var/constB = 0.140
var/constC = -0.283
return list(
LUMR + cos * (1-LUMR) + sin * -LUMR, LUMR + cos * -LUMR + sin * constA, LUMR + cos * -LUMR + sin * -(1-LUMR),
LUMG + cos * -LUMG + sin * -LUMG, LUMG + cos * (1-LUMG) + sin * constB, LUMG + cos * -LUMG + sin * LUMG,
LUMB + cos * -LUMB + sin * (1-LUMB), LUMB + cos * -LUMB + sin * constC, LUMB + cos * (1-LUMB) + sin * LUMB
LUMA_R + cos * (1-LUMA_R) + sin * -LUMA_R, LUMA_R + cos * -LUMA_R + sin * constA, LUMA_R + cos * -LUMA_R + sin * -(1-LUMA_R),
LUMA_G + cos * -LUMA_G + sin * -LUMA_G, LUMA_G + cos * (1-LUMA_G) + sin * constB, LUMA_G + cos * -LUMA_G + sin * LUMA_G,
LUMA_B + cos * -LUMA_B + sin * (1-LUMA_B), LUMA_B + cos * -LUMA_B + sin * constC, LUMA_B + cos * (1-LUMA_B) + sin * LUMA_B
)
//Makes everything brighter or darker without regard to existing color or brightness
/proc/color_brightness(power)
power = Clamp(power, -255, 255)
power = power/255
return list(1,0,0, 0,1,0, 0,0,1, power,power,power)
/var/list/delta_index = list(
var/global/list/delta_index = list(
0, 0.01, 0.02, 0.04, 0.05, 0.06, 0.07, 0.08, 0.1, 0.11,
0.12, 0.14, 0.15, 0.16, 0.17, 0.18, 0.20, 0.21, 0.22, 0.24,
0.25, 0.27, 0.28, 0.30, 0.32, 0.34, 0.36, 0.38, 0.40, 0.42,
@@ -83,9 +295,10 @@
7.3, 7.5, 7.8, 8.0, 8.4, 8.7, 9.0, 9.4, 9.6, 9.8,
10.0)
//Exxagerates or removes brightness
///Exxagerates or removes brightness
/proc/color_contrast(value)
value = Clamp(value, -100, 100)
RETURN_TYPE(/list)
value = round(clamp(value, -100, 100))
if(value == 0)
return color_identity()
@@ -104,24 +317,22 @@
var/add = 0.5 * (127-x) / 255
return list(mult,0,0, 0,mult,0, 0,0,mult, add,add,add)
//Exxagerates or removes colors
///Exxagerates or removes colors
/proc/color_saturation(value as num)
RETURN_TYPE(/list)
if(value == 0)
return color_identity()
value = Clamp(value, -100, 100)
value = clamp(value, -100, 100)
if(value > 0)
value *= 3
var/x = 1 + value / 100
var/inv = 1 - x
var/R = LUMR * inv
var/G = LUMG * inv
var/B = LUMB * inv
var/R = LUMA_R * inv
var/G = LUMA_G * inv
var/B = LUMA_B * inv
return list(R + x,R,R, G,G + x,G, B,B,B + x)
#undef LUMR
#undef LUMG
#undef LUMB
/**
*
@@ -131,10 +342,10 @@
* Do make sure your lists actually have this many elements
*/
/proc/multiply_matrices(list/A, list/B, m, n, p)
var/list/result = list()
result.len = m * p
RETURN_TYPE(/list)
var/list/result = new (m * p)
if(A.len == m*n && B.len == n*p)
if(length(A) == m*n && length(B) == n*p)
for(var/row = 1; row <= m; row += 1) //For each row on left matrix
for(var/col = 1; col <= p; col += 1) //go over each column of the second matrix
var/sum = 0
@@ -144,3 +355,8 @@
result[(row-1)*p + col] = sum
return result
#undef LUMA_R
#undef LUMA_G
#undef LUMA_B
+4 -9
View File
@@ -57,13 +57,11 @@
//A: atom to orbit
//radius: range to orbit at, radius of the circle formed by orbiting (in pixels)
//clockwise: whether you orbit clockwise or anti clockwise
//rotation_speed: how fast to rotate (how many ds should it take for a rotation to complete)
//rotation_segments: the resolution of the orbit circle, less = a more block circle, this can be used to produce hexagons (6 segments) triangles (3 segments), and so on, 36 is the best default.
//pre_rotation: Chooses to rotate src 90 degress towards the orbit dir (clockwise/anticlockwise), useful for things to go "head first" like ghosts
//pre_rotation: Chooses to rotate src 90 degress towards the orbit dir, useful for things to go "head first" like ghosts
//lockinorbit: Forces src to always be on A's turf, otherwise the orbit cancels when src gets too far away (eg: ghosts)
/atom/movable/proc/orbit(atom/A, radius = 10, clockwise = FALSE, rotation_speed = 20, rotation_segments = 36, pre_rotation = TRUE, lockinorbit = FALSE)
/atom/movable/proc/orbit(atom/A, radius = 10, rotation_speed = 20, pre_rotation = TRUE, lockinorbit = FALSE)
if (!istype(A))
return
@@ -75,17 +73,14 @@
//Head first!
if (pre_rotation)
var/matrix/M = matrix(transform)
var/pre_rot = 90
if(!clockwise)
pre_rot = -90
M.Turn(pre_rot)
M.Turn(90)
transform = M
var/matrix/shift = matrix(transform)
shift.Translate(0,radius)
transform = shift
SpinAnimation(rotation_speed, -1, clockwise, rotation_segments)
SpinAnimation(rotation_speed, -1)
//we stack the orbits up client side, so we can assign this back to normal server side without it breaking the orbit
transform = initial_transform
+1 -1
View File
@@ -196,7 +196,7 @@
var/orbitsize = (I.Width() + I.Height()) * pick(0.4, 0.5, 0.6, 0.7, 0.8)
orbitsize -= (orbitsize / world.icon_size) * (world.icon_size * 0.25)
EB.orbit(src, orbitsize, pick(FALSE, TRUE), rand(10, 25), pick(3, 4, 5, 6, 36))
EB.orbit(src, orbitsize, rand(10, 25))
/obj/singularity/energy_ball/Collide(atom/A)