Module:TestProtoFlux1

From Resonite Wiki
Revision as of 02:46, 9 February 2024 by 989onan (talk | contribs) (yeet)

This is a test for auto generated colors for nodes using CRC hashing like FrooxEngine does. Here are some examples:

Field As Variable<T>
Field
*
Core
System Empty type
Field
*
Core
Anchored User
Anchor
User
Anchors
CommonAnchor Anchored User
Anchor
User
Anchors

-- Package definition to return to Scribunto - this allows us to define methods that can be called
-- when this module is targeted.
local p = {}

-- Type colors - RGB values to be included in the HTML output when a color is needed. This will be replaced with CSS at some point.
local typeColor = 
{
	User = '255, 128, 255',
	Impulse = '179, 255, 255',
	bool = '115, 115, 115',
	AsyncImpulse = '204, 179, 255',
	String = '245, 31, 31',
	Dummy = '255, 0, 255',
	colorX = '255, 89, 0',
	float3 = '0, 255, 255',
	float2 = '0, 255, 255',
    ulong = '0, 255, 127',
    int = '0, 255, 0',
    Continuation = '255, 255, 178',
    Slot = '191, 255, 127',
	Uri = '123, 49, 186',
	int2 = '0, 255, 0',
	floatQ = '0, 127, 255'
}

local pack2 = {
	"int2",
	"float2",
	"uint2",
	"double2",
	"long2",
	"ulong2",
	"bool2"
}

local pack3 = {
	"int3",
	"float3",
	"uint3",
	"double3",
	"long3",
	"ulong3",
	"bool3"
}

local pack4 = {
	"int4",
	"float4",
	"uint4",
	"double4",
	"long4",
	"ulong4",
	"bool4"
}

--- Method that generates the HTML output
--	@param frame A Scribunto frame instance. frame.args contains the parameters passed into the module call
function p.GenerateUI( frame )
	-- Parse the JSON input, returning an empty array if no argument was passed
	local inputs = mw.text.jsonDecode(frame.args.Inputs or '[]')
	local outputs = mw.text.jsonDecode(frame.args.Outputs or '[]')
	local globals = mw.text.jsonDecode(frame.args.Globals or '[]')
	
	-- Create an HTML div element to contain our node UI
	local protofluxContainer = mw.html.create( 'div' )
	protofluxContainer
	    :attr('class', frame.args.Inline and '' or 'floatright')
		:cssText('color: rgb(224, 224, 224); background-color: rgb(18, 20, 28); width: 256px; display: flex; flex-direction: column; align-items: stretch;')
		:tag('div') -- HTML div to contain the node title
			:cssText('padding: 10px 0px 10px 0px; text-align:center; font-weight: bold; background-color: rgb(26, 41, 54); font-size: 18pt; flex-grow: 1;')
			:wikitext(frame.args.Name)
			:done() -- Close node title div
	
	local processedInputs = {}
	local processedOutputs = {}
	
	for i=1,#inputs do
		for j=1,(inputs and inputs[i].Multi or 1) do
			inputs[i].MultiIndex = j
			table.insert(processedInputs, { Name=inputs[i].Name, Type=inputs[i].Type, Multi=inputs[i].Multi, MultiIndex=j })
		end
	end
	
	for i=1,#outputs do
		for j=1,(outputs and outputs[i].Multi or 1) do
			table.insert(processedOutputs, { Name=outputs[i].Name, Type=outputs[i].Type, Multi=outputs[i].Multi, MultiIndex=j })
		end
	end
	
	-- Calculate the larger number of rows required (either for inputs or outputs, if the node is asymmetric)
	local maxRows = math.max(#processedInputs, #processedOutputs)
	
	
	-- Iterate over each row, and populate it with the inputs/outputs. If the node is asymmetric,
	-- the value passed for either input or output might be nil.
	
	for i=1,maxRows do
		CreateConnectorRow(protofluxContainer, processedInputs[i], processedOutputs[i])
	end
	
	-- Iterate over each global value in the node, and create a row. These elements take up the entire width
	-- of the node, and so don't need to be balanced in any way.
	for i=1,#globals do
		CreateGlobalsRow(protofluxContainer, globals[i])
	end
	
	protofluxContainer
		:tag('div') -- HTML div to contain the node category footer
			:cssText('text-align: center; padding: 10px; font-size: 18pt; color: rgb(64,64,64); font-weight: bold;')
			:wikitext(frame.args.Category)
			:done() -- Close category footer div
	
	-- Return the HTML generated above to the wiki page this script is invoked from.
	return tostring(protofluxContainer) .. '[[Category:ProtoFlux:All]]'
end

--- Creates a new ProtoFlux connector row in the output
-- @param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
-- @param Input Table containing a Name and Type for the input on this row. Can be nil if no input is to be placed on this row.
-- @param Output Table containing a Name and Type for the output on this row. Can be nil if no output is to be placed on this row.
function CreateConnectorRow(Container, Input, Output)
	local connectorRow = Container
		:tag('div') -- HTML div to contain the connector row. We specify a min-height of 70px for consistency.
			:cssText('display: flex; min-height: 70px;')
	
	-- Create the input (left) attachement point
	CreateConnectorAttachmentPoint(connectorRow, Input, true)

	local c = connectorRow
		:tag('div') -- HTML div to contain the input and output labels in the connector row
			:cssText( 'flex-grow: 2; overflow: hidden; display: flex; flex-direction: column; justify-content: space-between;')
	if Input and Input.Multi and Input.MultiIndex == 1 then
		c
			:tag('div') -- HTML div to contain the input label
				:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;  border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
				:wikitext(Input and Input.Name or '' )
				:done() -- Close input label div
	elseif Input and Input.Multi and Input.MultiIndex > 1 then
		local c2 = c
			:tag('div') -- HTML div to contain the input label
				:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('text-align: left; padding-left: 4px;  border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
		if Input.Multi == Input.MultiIndex then
			c2
				:tag('div')
					:cssText('position:relative; top:2rem; display:flex; gap: 4px;')
					:tag('div')
						:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
						:wikitext('+')
						:done()
					:tag('div')
						:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
						:wikitext('-')
						:done()
		end
	else
		c
			:tag('div') -- HTML div to contain the input label
				:attr('title', Input and (Input.Name .. ' <' .. Input.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('text-align: left; overflow: hidden; text-overflow: ellipsis; padding-left: 4px;  border-right: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color:' .. GetTypeColor(Input, 0.6) .. ';')
				:wikitext(Input and Input.Name or '' )
				:done() -- Close input label div
	end
	if Output and Output.Multi and Output.MultiIndex == 1 then
		c
			:tag('div') -- HTML div to contain the output label
				:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;')
				:wikitext(Output and Output.Name or '')
				:done() -- Close output label div
	elseif Output and Output.Multi and Output.MultiIndex > 1 then
		local c2 = c
			:tag('div') -- HTML div to contain the output label
				:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('text-align: right; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: transparent;;')
		if Output.Multi == Output.MultiIndex then
			c2
				:tag('div')
					:cssText('display:flex; justify-content:flex-end; gap: 4px;')
					:tag('div')
						:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
						:wikitext('+')
						:done()
					:tag('div')
						:cssText('font-size: 1.75rem; width: 1em; height: 1em; background-color:grey; border-radius: 2em; display:flex; align-items:center; justify-content:center;')
						:wikitext('-')
						:done()
		end
	else
		c
		:tag('div') -- HTML div to contain the output label
			:attr('title', Output and (Output.Name .. ' <' .. Output.Type .. '>') or '') -- Add a basic mouseover description
			:cssText('text-align: right; overflow: hidden; text-overflow: ellipsis; padding-right: 4px; border-left: 20px solid #11151d; border-bottom: 4px solid #11151d; border-top: 4px solid #11151d; background-color: ' .. GetTypeColor(Output, 0.6) .. ';')
			:wikitext(Output and Output.Name or '')
			:done() -- Close output label div
	end	
	-- Create the output (right) attachment point
	CreateConnectorAttachmentPoint(connectorRow, Output, false);
end

--- Creates a new ProtoFlux global input field row in the output
--	@param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
--	@param Global Table containing a Name and Type for the global field on this row.
function CreateGlobalsRow(Container, Global)
	Container
		:tag( 'div' )
			:attr('title', Global.Name .. ' <' .. Global.Type .. '>') -- Add a basic mouseover description
			:cssText('display: flex; min-height: 70px; flex-direction: column; border-left: 10px solid ' .. GetTypeColor(Global, 1.0) .. ';')
			:tag( 'div' )
				:cssText('display: flex; flex-direction: row; align-items: center; flex-grow:1; overflow: hidden;')
				:tag( 'span' )
					:cssText('text-align: center; overflow: hidden; text-overflow: ellipsis; font-size: 14pt; font-weight: bold; flex-grow:1;')
					:wikitext(Global.Name)
					:done()
				:done()
			:tag( 'div' )
				:attr('style', 'display:flex; gap: 10px; flex-grow: 1;')
				:tag('div')
					:cssText('border-radius: 16px; background-color: #777; font-style: italic; text-align:center; flex-grow: 3; display: flex; flex-direction: column; justify-content: center;')
					:tag( 'span' )
						:wikitext('null')
						:done()
					:done()
				:tag( 'div')
					:cssText('border-radius: 16px; background-color: #333; text-align:center; flex-grow: 1; display: flex; flex-direction: column; justify-content: center;')
					:tag ( 'span' )
						:wikitext('∅')   
end

--- Creates a new input or output attachement point for a connector row.
--	@param Container The Scribunto HTML node we'll be creating this row inside of. Should be a container of some sort.
--	@param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
--	@param isInput	True if this is an input side attachment point, false if it is an output side attachment point.
function CreateConnectorAttachmentPoint(Container, Connector, isInput)
	if Connector and (Connector.Type == "Impulse" or Connector.Type == "AsyncImpulse" or Connector.Type == "Continuation")  then
		Container
			:tag( 'div' )
				:attr('title', Connector and (Connector.Name .. ' <' .. Connector.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('width:30px; background-color: black; fill: ' .. GetTypeColor(Connector, 0.3) .. '; stroke: ' .. GetTypeColor(Connector, 1.0))
				:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type="InputArrow"}}))

	elseif Connector then
		Container
			:tag( 'div' )
				:attr('title', Connector and (Connector.Name .. ' <' .. Connector.Type .. '>') or '') -- Add a basic mouseover description
				:cssText('width:30px; fill: ' .. GetTypeColor(Connector, 0.3) .. '; stroke: ' .. GetTypeColor(Connector, 1.0))
				:wikitext(mw.getCurrentFrame():expandTemplate({title = 'ProtoFluxConnector', args = {Type=(isInput and "InputBox" or "OutputBox")}}))
	end
end

--- Returns an RGBA value representing the type color within Resonite
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
-- @param Alpha The alpha to use in the RGBA value. 
function GetTypeColor(Connector, Alpha)
	return (Connector and 'rgba(' .. (typeColor[Connector.Type] or hashToColor(Hash(Connector.Type))) .. ',' .. Alpha .. ')' or 'rgba(0, 0, 0, 0)')
end

--- Returns an RGBA value representing the type color within Resonite using CRC hash
--- this code was made using decompiled dlls of Resonite as reference.
-- @param Connector Table containing a Name (Optional) and Type (Required) for the input/output attachment point.
-- @param Alpha The alpha to use in the RGBA value. 
function hashToColor(crc)
	
	
	local h = BitAND(crc, 255) /255;
	local s = BitAND(rshift(crc, 8), 255) /255;
	local v = BitAND(rshift(crc, 16), 255) /255;
	
	s = lerpunclamped(0.25,0.75,s);
	v = lerpunclamped(0.25,1,v);
	local r,g,b = hsvToRgb(h, s, v, 1);
	
	
	
	
	
	return ((h) .. "," .. (s) .. "," .. (v));
	
	
	
	
	
end


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

-- https://github.com/EmmanuelOga/columns/blob/master/utils/color.lua
function hsvToRgb(h, s, v, a)
  local r, g, b

  local i = math.floor(h * 6);
  local f = h * 6 - i;
  local p = v * (1 - s);
  local q = v * (1 - f * s);
  local t = v * (1 - (1 - f) * s);

  i = i % 6

  if i == 0 then r, g, b = v, t, p
  elseif i == 1 then r, g, b = q, v, p
  elseif i == 2 then r, g, b = p, v, t
  elseif i == 3 then r, g, b = p, q, v
  elseif i == 4 then r, g, b = t, p, v
  elseif i == 5 then r, g, b = v, p, q
  end

  return r * 255, g * 255, b * 255
end

--https://stackoverflow.com/a/25594410
function BitAND(a,b)--Bitwise and
    local p,c=1,0
    while a>0 and b>0 do
        local ra,rb=a%2,b%2
        if ra+rb>1 then c=c+p end
        a,b,p=(a-ra)/2,(b-rb)/2,p*2
    end
    return c
end


--Copyright (c) 2007-2008 Neil Richardson (nrich@iinet.net.au)
--
--Permission is hereby granted, free of charge, to any person obtaining a copy 
--of this software and associated documentation files (the "Software"), to deal
--in the Software without restriction, including without limitation the rights 
--to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 
--copies of the Software, and to permit persons to whom the Software is 
--furnished to do so, subject to the following conditions:
--
--The above copyright notice and this permission notice shall be included in all
--copies or substantial portions of the Software.
--
--THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
--IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
--FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
--AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
--LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
--OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 
--IN THE SOFTWARE.

local max = 2^32 -1

local CRC32 = {
    0,79764919,159529838,222504665,319059676,
    398814059,445009330,507990021,638119352,
    583659535,797628118,726387553,890018660,
    835552979,1015980042,944750013,1276238704,
    1221641927,1167319070,1095957929,1595256236,
    1540665371,1452775106,1381403509,1780037320,
    1859660671,1671105958,1733955601,2031960084,
    2111593891,1889500026,1952343757,2552477408,
    2632100695,2443283854,2506133561,2334638140,
    2414271883,2191915858,2254759653,3190512472,
    3135915759,3081330742,3009969537,2905550212,
    2850959411,2762807018,2691435357,3560074640,
    3505614887,3719321342,3648080713,3342211916,
    3287746299,3467911202,3396681109,4063920168,
    4143685023,4223187782,4286162673,3779000052,
    3858754371,3904687514,3967668269,881225847,
    809987520,1023691545,969234094,662832811,
    591600412,771767749,717299826,311336399,
    374308984,453813921,533576470,25881363,
    88864420,134795389,214552010,2023205639,
    2086057648,1897238633,1976864222,1804852699,
    1867694188,1645340341,1724971778,1587496639,
    1516133128,1461550545,1406951526,1302016099,
    1230646740,1142491917,1087903418,2896545431,
    2825181984,2770861561,2716262478,3215044683,
    3143675388,3055782693,3001194130,2326604591,
    2389456536,2200899649,2280525302,2578013683,
    2640855108,2418763421,2498394922,3769900519,
    3832873040,3912640137,3992402750,4088425275,
    4151408268,4197601365,4277358050,3334271071,
    3263032808,3476998961,3422541446,3585640067,
    3514407732,3694837229,3640369242,1762451694,
    1842216281,1619975040,1682949687,2047383090,
    2127137669,1938468188,2001449195,1325665622,
    1271206113,1183200824,1111960463,1543535498,
    1489069629,1434599652,1363369299,622672798,
    568075817,748617968,677256519,907627842,
    853037301,1067152940,995781531,51762726,
    131386257,177728840,240578815,269590778,
    349224269,429104020,491947555,4046411278,
    4126034873,4172115296,4234965207,3794477266,
    3874110821,3953728444,4016571915,3609705398,
    3555108353,3735388376,3664026991,3290680682,
    3236090077,3449943556,3378572211,3174993278,
    3120533705,3032266256,2961025959,2923101090,
    2868635157,2813903052,2742672763,2604032198,
    2683796849,2461293480,2524268063,2284983834,
    2364738477,2175806836,2238787779,1569362073,
    1498123566,1409854455,1355396672,1317987909,
    1246755826,1192025387,1137557660,2072149281,
    2135122070,1912620623,1992383480,1753615357,
    1816598090,1627664531,1707420964,295390185,
    358241886,404320391,483945776,43990325,
    106832002,186451547,266083308,932423249,
    861060070,1041341759,986742920,613929101,
    542559546,756411363,701822548,3316196985,
    3244833742,3425377559,3370778784,3601682597,
    3530312978,3744426955,3689838204,3819031489,
    3881883254,3928223919,4007849240,4037393693,
    4100235434,4180117107,4259748804,2310601993,
    2373574846,2151335527,2231098320,2596047829,
    2659030626,2470359227,2550115596,2947551409,
    2876312838,2788305887,2733848168,3165939309,
    3094707162,3040238851,2985771188,
}

function xor(a, b)
    local calc = 0    

    for i = 32, 0, -1 do
	local val = 2 ^ i
	local aa = false
	local bb = false

	if a == 0 then
	    calc = calc + b
	    break
	end

	if b == 0 then
	    calc = calc + a
	    break
	end

	if a >= val then
	    aa = true
	    a = a - val
	end

	if b >= val then
	    bb = true
	    b = b - val
	end

	if not (aa and bb) and (aa or bb) then
	    calc = calc + val
	end
    end

    return calc
end

function lshift(num, left)
    local res = num * (2 ^ left)
    return res % (2 ^ 32)
end

function rshift(num, right)
    local res = num / (2 ^ right)
    return math.floor(res)
end

function Hash(str)
    local count = string.len(tostring(str))
    local crc = max
    
    local i = 1
    while count > 0 do
	local byte = string.byte(str, i)

	crc = xor(lshift(crc, 8), CRC32[xor(rshift(crc, 24), byte) + 1])

	i = i + 1
	count = count - 1
    end

    return crc
end




return p