Module:ProtoFlux Type Color

From Resonite Wiki

Code for finding the colors of C# types, translated from the Resonite source code.

Invokable Functions
Function Purpose Example
get_impulse_color_css Gets the color of an impulse {{#invoke:ProtoFlux_Type_Color|get_impulse_color_css|Continuation}}
get_type_color_css Gets the color of a data type {{#invoke:ProtoFlux_Type_Color|get_type_color_css|User}}
get_color_css First attempts to interpret the type as an impulse {{#invoke:ProtoFlux_Type_Color|get_color_css|int}}

The following functions are exported, but not invokable by Media Wiki.

Other Functions
Function Purpose Example
get_impulse_color Gets the color of an impulse get_impulse_color("Continuation")
get_type_color Gets the color of a data type get_type_color("User")
color_from_crc Derives a color from the given CRC32 checksum color_from_crc(0x9CD97842)

How exactly these invokations may be used can be seen by inspecting the source of this wiki and related templates.

Examples

Continuation Call AsyncCall SyncResumption AsyncResumption

Types

Values and String

Signed

sbyte short int long

Unsigned

byte ushort uint ulong

Decimal

float double decimal

String

String

Non-Values

A

Animation AssetLoadState AudioDistanceSpace AudioOutput AudioRolloffMode AudioTypeGroup

B

bobool3ol BodyNode BoundingBox BoundingBox2D

C

Camera CharacterController ColorProfile Component ComponentHandling CultureInfo CurvePreset

D

DateTimeKind DayOfWeek

E

EventState

F

FingerSegmentType FingerType

G

Grabber Guid

H

half

I

IAssetProvider IAssetProvider`1 IAvatarAnchor ICollider IComponent IField IField`1 IFingerPoseSource IFocusable IFormatProvider IGrabbable ILocomotionModule IPlayable ISyncRef ISyncRef`1 ITool ITouchable IValue IValue`1 IVariable`2 IWorldElement IWorldLink

J

JoinRequestHandle

K
L

LocaleResource

M
N

Nullable<T> Nullable`1

O

Object

P

Platform ProtoFluxNode

Q
R

RawDataTool Rect RefID ReflectionProbe

S

Slot SpriteProvider StaticAudioClip StaticMesh StaticTexture2D StaticTexture3D StringComparison SyncPlayback

T

TangentPointFloat Texture2D Texture3D TextureFormat TextureWrapMode TouchEventType TouchType

U

User UserRef UserRoot UsersAssetLoadProgress`1

V
W

WebsocketClient WorldRelation WrapMode

XYZ

-- Code for finding the colors of C# types, translated from the Resonite source code
-- Source: FrooxEngine.ProtoFlux.DatatypeColorHelper (decompiled with ILSpy)
-- Binary found at Resonite_Data/Managed/FrooxEngine.dll

local p = {}

local checksum = require("Module:CRC_32_ISO_HDLC")
local bit32 = require("bit32")

local colors = {
  CONTINUATION = {r=1, g=1, b=0.7},
  SYNC_FLOW = {r=0.7, g=1, b=1},
  ASYNC_FLOW = {r=0.8, g=0.7, b=1},
  SYNC_RESUMPTION = {r=0.7, g=0.7, b=1},
  ASYNC_RESUMPTION = {r=1, g=0.7, b=0.7},
  INPUT_LABEL = {r=0.8, g=0.9, b=1},
  OUTPUT_LABEL = {r=0.9, g=0.8, b=1},
  COLOR_BASE = {r=1, g=1, b=0},
  COLORX_BASE = {r=1, g=0.5, b=0},
  SIGNED_INT_BASE = {r=0, g=1, b=0},
  UNSIGNED_INT_BASE = {r=0, g=1, b=0.5},
  FLOAT_BASE = {r=0, g=1, b=1},
  QUATERNION_BASE = {r=0, g=0.5, b=1},
  DECIMAL_BASE = {r=0, g=0, b=1},
  BOOL_BASE = {r=0.3, g=0.3, b=0.3},
  CHAR_BASE = {r=0.6392157, g=37/85, b=7/85},
  STRING_BASE = {r=0.6392157, g=7/85, b=7/85},
  SLOT_BASE = {r=0.75, g=1, b=0.5},
  USER_BASE = {r=1, g=0.5, b=1},
  DUMMY = {r=1, g=0, b=1},
}

local primitive_class = {
  bool="boolean",
  char="char",
  String="string",
  byte="unsigned_integer",
  ushort="unsigned_integer",
  uint="unsigned_integer",
  ulong="unsigned_integer",
  sbyte="signed_integer",
  short="signed_integer",
  int="signed_integer",
  long="signed_integer",
  float="floating_point",
  double="floating_point",
  decimal="floating_point",
}

local primitive_class_rank = {
  byte=0,
  sbyte=0,
  float=0,
  ushort=1,
  short=1,
  double=1,
  uint=2,
  int=2,
  ulong=3,
  long=3,
}

local max_primitive_class_rank = {
  boolean=0,
  string=0,
  char=0,
  floating_point=1,
  signed_integer=3,
  unsigned_integer=3,
}

local primitive_class_base_color = {
  signed_integer=colors.SIGNED_INT_BASE,
  unsigned_integer=colors.UNSIGNED_INT_BASE,
  floating_point=colors.FLOAT_BASE,
}

function p.get_impulse_color(type_str)
  if type_str == "Continuation" then return colors.CONTINUATION
  elseif type_str == "Call" then return colors.SYNC_FLOW
  elseif type_str == "AsyncCall" then return colors.ASYNC_FLOW
  elseif type_str == "SyncResumption" then return colors.SYNC_RESUMPTION
  elseif type_str == "AsyncResumption" then return colors.ASYNC_RESUMPTION
  end
end

function p.get_type_color(type_str, rec_limit)
  if rec_limit == 0 then error("Recursion limit reached")
  elseif rec_limit == nil then rec_limit = 4
  end

  local btype = get_base_type(type_str)
  
  if btype == "bool" then return colors.BOOL_BASE
  elseif btype == "char" then return colors.CHAR_BASE
  elseif btype == "String" then return colors.STRING_BASE
  elseif btype == "Dummy" then return colors.DUMMY
  elseif btype == "color" then return colors.COLOR_BASE
  elseif btype == "colorX" then return colors.COLORX_BASE
  elseif btype == "decimal" then return colors.DECIMAL_BASE
  elseif btype == "Slot" then return colors.SLOT_BASE
  elseif btype == "User" then return colors.USER_BASE
  elseif p.get_impulse_color(btype) then return p.get_impulse_color(btype)
  end
  
  local prim_cls = primitive_class[btype]
  if prim_cls ~= nil then
    local rank = primitive_class_rank[btype]
    local max_rank = max_primitive_class_rank[prim_cls]
    local rank_lerp = inverse_lerp(0, max_rank, rank)
    local base_color = primitive_class_base_color[prim_cls]
    if base_color == nul then error("Invalid primitive class: "..prim_cls) end
    return lerp_primitive_color(base_color, rank_lerp)
  end
  
  -- Vector or Matrix (eg. float3, float3x3)
  if btype:match("^%a+%d$") ~= nil or btype:match("^%a+%dx%d$") ~= nil then
    local vec_btype = btype:match("^%a+")
    if primitive_class[vec_btype] ~= nil then
      return p.get_type_color(vec_btype, rec_limit - 1)
    end
  end
  
  -- Quaternion
  if btype:match("^%a+Q$") ~= nil then
    local q_btype = btype:sub(1,-2)
    local rank = nil
    if q_btype == "float" then rank = 0
    elseif q_btype == "double" then rank = 1
    end
    if rank ~= nil then
      return lerp_primitive_color(colors.QUATERNION_BASE, rank)
    end
  end
  
  -- Nullable
  if btype == "Nullable" then
    local i = string.find(type_str, "<", 0, true)
    local last = type_str:sub(-1)
    if i == nil or last ~= ">" then error("Invalid Nullable") end
    local color = p.get_type_color(type_str:sub(i + 1, -2), rec_limit - 1)
    color.a = (color.a or 1) * 0.5
    return color
  end
  
  return p.color_from_crc(checksum.crc32(btype))
end

function p.color_from_crc(value)
  local h = bit32.band(value, 0xff) / 255
  local lerp_s = bit32.band(bit32.rshift(value, 8), 0xff) / 255
  local lerp_v = bit32.band(bit32.rshift(value, 16), 0xff) / 255
  local s = lerp_unclamped(0.25, 0.75, lerp_s)
  local v = lerp_unclamped(0.25, 1.00, lerp_v)
  return hsv_to_rgb(h, s, v)
end

function p.get_impulse_color_css(frame)
  local color = p.get_impulse_color(frame.args[1])
  return color and color_to_css(color)
end

function p.get_type_color_css(frame)
  local color = p.get_type_color(frame.args[1])
  return color_to_css(color, 1.5)
end

function p.get_color_css(frame)
  local text = frame.args[1]
  local factor = 1
  local color = p.get_impulse_color(text)
  if color == nil then
    factor, color = 1.5, p.get_type_color(text)
  end
  return color_to_css(color, factor)
end

function color_to_css(color, factor)
  local mult = 255 * (factor or 1)
  return ("rgba(%s,%s,%s,%s)"):format(color.r*mult, color.g*mult, color.b*mult, color.a or 1)
end

function get_base_type(type_str)
  local i = type_str:find("<", 0, true)
  if i == nil then
    return type_str
  else
    return type_str:sub(1, i - 1)
  end
end

function lerp_primitive_color(base, rank)
  return lerp_color({r=0,g=0,b=0}, base, 0.75 + rank * 0.25)
end

function lerp_color(a, b, lerp)
  if lerp <= 0 then return a end
  if lerp >= 1 then return b end
  return lerp_unclamped_color(a, b, lerp)
end

function inverse_lerp(a, b, lerp)
  return (lerp - a) / (b - a)
end

function lerp_unclamped(a, b, lerp)
  return a + (b - a) * lerp
end

function lerp_unclamped_color(a, b, lerp)
  local prel = 1 - lerp
  local alpha = 1
  if a.a ~= nil and b.a ~= nil then
    alpha = a.a * prel + b.a * lerp
  end
  return {
    r=a.r * prel + b.r * lerp,
    g=a.g * prel + b.g * lerp,
    b=a.b * prel + b.b * lerp,
    a=alpha,
  }
end

function num_repeat(v, length)
  return v - math.floor(v / length) * length
end

function hsv_to_rgb(h, s, v)
  local gain = math.max(v, 1)
  v = v / gain
  local vs = v * s
  local f = vs * (1 - math.abs(num_repeat(h * 6, 2) - 1))
  local light = v - vs
  
  local r,g,b = 0,0,0
  local switch_on = math.floor(num_repeat(h, 1) / (1/6))
  if     switch_on == 0 then r,g,b = vs,f,0
  elseif switch_on == 1 then r,g,b = f,vs,0
  elseif switch_on == 2 then r,g,b = 0,vs,f
  elseif switch_on == 3 then r,g,b = 0,f,vs
  elseif switch_on == 4 then r,g,b = f,0,vs
  elseif switch_on == 5 then r,g,b = vs,0,f
  end
  
  return {
    r=(r+light)*gain,
    g=(g+light)*gain,
    b=(b+light)*gain
  }
end

return p