├── .gitignore ├── README.md ├── biker.dff ├── biker.txd ├── class.lua ├── gfriend.dff ├── gfriend.txd ├── math_client.lua ├── math_condchains_linopt.lua ├── math_condtransform.lua ├── math_linearconds_2d.lua ├── math_server.lua ├── math_server_testing.lua ├── math_shared.lua ├── math_shared_absmirr.lua ├── mathcore ├── math_basiccond.lua ├── math_condchains.lua └── math_utilities.lua ├── meta.xml ├── progress_feedback_client.lua ├── progress_feedback_server.lua ├── reqdebugdraw_client.lua ├── reqdebugdraw_server.lua ├── rw_server.lua ├── rw_shared.lua ├── shared.lua ├── single_test_draw.lua ├── threads.lua └── utilities_vendor.lua /.gitignore: -------------------------------------------------------------------------------- 1 | /debug_tri_draw.lua 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mta_lua_3d_math 2 | Lua implementation of 3D frustum-plane intersection written for Multi Theft Auto San Andreas. 3 | 4 | Uses Linear Algebra for software rendering 3D polygons. Comes with a RenderWare DFF parser so you can draw models from GTA:SA. 5 | 6 | Uses some code from other sources. Credit is given where it is due (i.e. StackOverflow). 7 | 8 | ## Useful commands 9 | 10 | After starting this resource you can execute the following commands for testing: 11 | 12 | * send_bbuf: draw a test scene defined at the top of math_server.lua 13 | * draw_model: renders a DFF file 14 | * tridraw: performs per-frame frustum checking of a triangle located at the middle of GTA:SA map 15 | 16 | ## External References 17 | 18 | * Forum Discussion (German): https://www.mta-sa.org/thread/38693-3d-frustum-ebene-schneidung-in-lua/ 19 | * Forum Discussion (English): https://forum.mtasa.com/topic/122576-software-rendering-in-mtasa-lua/ 20 | 21 | ## Math calculation samples 22 | * https://imgur.com/gallery/rLvln3X 23 | * https://imgur.com/gallery/ffC08OT -------------------------------------------------------------------------------- /biker.dff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quiret/mta_lua_3d_math/5e6b7cc00b62a3f3002876773b0312219571d639/biker.dff -------------------------------------------------------------------------------- /biker.txd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quiret/mta_lua_3d_math/5e6b7cc00b62a3f3002876773b0312219571d639/biker.txd -------------------------------------------------------------------------------- /class.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations 2 | local outputDebugString = outputDebugString; 3 | local rawset = rawset; 4 | local rawget = rawget; 5 | local setmetatable = setmetatable; 6 | local setfenv = setfenv; 7 | local error = error; 8 | local type = type; 9 | local unpack = unpack; 10 | local pairs = pairs; 11 | local ipairs = ipairs; 12 | local debug = debug; 13 | local table = table; 14 | local tinsert = table.insert; 15 | 16 | if not (table.delete) then 17 | -- We assume the already-present implementation works. 18 | -- Kind of a wild guess, really. 19 | function table.delete(t, v) 20 | for m,n in ipairs(t) do 21 | if (n == v) then 22 | table.remove(t, m); 23 | return true; 24 | end 25 | end 26 | 27 | error("failed to delete value from table", 2); 28 | return false; 29 | end 30 | end 31 | 32 | local classNoAccess = { 33 | super = true 34 | }; 35 | 36 | local function createClass(members, forcedSuper) 37 | local class = members or {}; 38 | local meta = {}; 39 | local methods = {}; 40 | local classmeta = {}; 41 | local methodenv = {}; 42 | local forceSuperHandlers = {}; 43 | local m,n; 44 | local reqDestruction = false; 45 | local destroying = false; 46 | local inMethod = 0; 47 | local refCount = 0; 48 | local lastDestroy; 49 | local children = {}; 50 | local cAPIs = {}; 51 | local childAPI; 52 | local _G = debug.getfenv(debug.getinfo(2).func); 53 | local setmetatable = setmetatable; 54 | 55 | -- Compatibility values 56 | class._ENV = methodenv; 57 | 58 | if (forcedSuper) then 59 | for m,n in pairs(forcedSuper) do 60 | forceSuperHandlers[m] = function(method) 61 | local submethod = methods[m]; 62 | local function subCall() 63 | error("cannot call submethod in '" .. m .. "'", 2); 64 | end 65 | 66 | if (submethod) then 67 | return function(...) 68 | local previous = methods.super; 69 | 70 | methods.super = subCall; 71 | 72 | inMethod = inMethod + 1; 73 | method(...); 74 | inMethod = inMethod - 1; 75 | 76 | methods.super = previous; 77 | return submethod(...); 78 | end 79 | end 80 | 81 | return function(...) 82 | local previous = methods.super; 83 | 84 | methods.super = subCall; 85 | 86 | inMethod = inMethod + 1; 87 | method(...); 88 | inMethod = inMethod - 1; 89 | 90 | methods.super = previous; 91 | 92 | if (reqDestruction) and (inMethod == 0) then 93 | class.destroy(); 94 | end 95 | end 96 | end 97 | end 98 | end 99 | 100 | local function preDestructor() 101 | local destructor = methods.destroy; 102 | 103 | function methods.destroy() 104 | error("attempted to destroy " .. class.getType() .. " during destruction", 2); 105 | end 106 | 107 | destroying = true; 108 | 109 | local m,n; 110 | 111 | n = 1; 112 | 113 | while (n <= #children) do 114 | local child = children[n]; 115 | 116 | if not (child.isDestroying()) then 117 | if not (child.destroy()) then 118 | n = n + 1; 119 | end 120 | else 121 | n = n + 1; 122 | end 123 | end 124 | 125 | if not (#children == 0) then 126 | outputDebugString("DEBUG " .. getType(), 2); 127 | 128 | for m,n in pairs(cAPIs) do 129 | function n.cleanUp() 130 | if not (#children == 0) then return; end; 131 | 132 | outputDebugString("DESTR " .. class.getType(), 1); 133 | 134 | destructor(); 135 | end 136 | end 137 | 138 | return false; 139 | end 140 | 141 | if (childAPI) then 142 | childAPI.destroy(); 143 | end 144 | 145 | rawset(class, "children", {}); 146 | return true; 147 | end 148 | 149 | function forceSuperHandlers.destroy(method) 150 | local inherit; 151 | 152 | if not (lastDestroy) then 153 | function inherit() 154 | if not (inMethod == 0) then 155 | reqDestruction = true; 156 | return false; 157 | end 158 | 159 | reqDestruction = false; 160 | 161 | if not (preDestructor()) then 162 | return false; 163 | end 164 | 165 | method(); 166 | return true; 167 | end 168 | 169 | lastDestroy = method; 170 | else 171 | local prev = lastDestroy; 172 | 173 | function inherit() 174 | if not (inMethod == 0) then 175 | reqDestruction = true; 176 | return false; 177 | end 178 | 179 | reqDestruction = false; 180 | 181 | if not (preDestructor()) then 182 | return false; 183 | end 184 | 185 | method(); 186 | prev(); 187 | return true; 188 | end 189 | 190 | function lastDestroy() 191 | method(); 192 | return prev(); 193 | end 194 | end 195 | 196 | return inherit; 197 | end 198 | 199 | function classmeta.__index(t, key) 200 | local item = rawget(methods, key); 201 | 202 | if not (item == nil) then 203 | return item; 204 | end 205 | 206 | item = rawget(class, key); 207 | 208 | if not (item == nil) then 209 | return item; 210 | end 211 | 212 | return _G[key]; 213 | end 214 | 215 | function classmeta.__newindex(t, key, value) 216 | if (type(value) == "function") then 217 | class[key] = value; 218 | return true; 219 | elseif (methods[key]) then 220 | error("cannot overwrite method '" .. key .. "'", 2); 221 | end 222 | 223 | rawset(class, key, value); 224 | return true; 225 | end 226 | 227 | classmeta.__metatable = false; 228 | setmetatable(methodenv, classmeta); 229 | 230 | function methods.__newindex(tab, key, value) 231 | if (classNoAccess[key]) then 232 | error("invalid member overload '" .. key .. "'", 2); 233 | end 234 | 235 | if (type(value) == "function") then 236 | local method; 237 | local superHandler = forceSuperHandlers[key]; 238 | 239 | if (superHandler) then 240 | method = superHandler(value); 241 | else 242 | local submethod = methods[key]; 243 | 244 | if (submethod) then 245 | function method(...) 246 | local previous = methods.super; 247 | local ret; 248 | 249 | methods.super = submethod; 250 | 251 | inMethod = inMethod + 1; 252 | ret = { value(...) }; 253 | inMethod = inMethod - 1; 254 | 255 | methods.super = previous; 256 | 257 | if (reqDestruction) and (inMethod == 0) then 258 | class.destroy(); 259 | end 260 | 261 | return unpack(ret); 262 | end 263 | else 264 | function method(...) 265 | local previous = methods.super; 266 | local ret; 267 | 268 | methods.super = false; 269 | 270 | inMethod = inMethod + 1; 271 | ret = { value(...) }; 272 | inMethod = inMethod - 1; 273 | 274 | methods.super = previous; 275 | 276 | if (reqDestruction) and (inMethod == 0) then 277 | class.destroy(); 278 | end 279 | 280 | return unpack(ret); 281 | end 282 | end 283 | end 284 | 285 | setfenv(value, methodenv); 286 | 287 | rawset(methods, key, method); 288 | return true; 289 | elseif (methods[key]) then 290 | error("'" .. key .. "' cannot overwrite method with " .. type(value), 2); 291 | end 292 | 293 | rawset(class, key, value); 294 | return true; 295 | end 296 | 297 | function methods.__index(t, k) 298 | return rawget(methods, k); 299 | end 300 | 301 | -- Make sure this stays a class 302 | methods.__metatable = class; 303 | setmetatable(class, methods); 304 | 305 | -- Reference system to avoid illegal destructions 306 | function class.reference() 307 | local m,n; 308 | 309 | for m,n in ipairs(children) do 310 | n.reference(); 311 | end 312 | 313 | inMethod = inMethod + 1; 314 | refCount = refCount + 1; 315 | return true; 316 | end 317 | 318 | function class.incrementMethodStack() 319 | inMethod = inMethod + 1; 320 | end 321 | 322 | function class.decrementMethodStack() 323 | inMethod = inMethod - 1; 324 | end 325 | 326 | function class.dereference() 327 | local n = 1; 328 | 329 | while (n <= #children) do 330 | if (children[n].dereference()) then 331 | n = n + 1; 332 | end 333 | end 334 | 335 | inMethod = inMethod - 1; 336 | refCount = refCount - 1; 337 | return not (reqDestruction) or not (inMethod == 1); 338 | end 339 | 340 | function class._aref(cnt) 341 | inMethod = inMethod + cnt; 342 | refCount = cnt; 343 | return true; 344 | end 345 | 346 | function class._dref() 347 | inMethod = inMethod - refCount; 348 | refCount = 0; 349 | return true; 350 | end 351 | 352 | function class.getType() 353 | return "class"; 354 | end 355 | 356 | function class.setGlobal(g) 357 | _G = g; 358 | return true; 359 | end 360 | 361 | function class.getGlobal() 362 | return _G; 363 | end 364 | 365 | function class.setChild(child) 366 | local childAPI = createClass(); 367 | 368 | function childAPI.cleanUp() 369 | return; 370 | end 371 | 372 | function childAPI.destroy() 373 | child._dref(); 374 | 375 | table.delete(children, child); 376 | cAPIs[child] = nil; 377 | 378 | cleanUp(); 379 | end 380 | 381 | child._aref(refCount); 382 | 383 | tinsert(children, child); 384 | cAPIs[child] = childAPI; 385 | return childAPI; 386 | end 387 | 388 | function class.setParent(c) 389 | if (childAPI) then 390 | childAPI.destroy(); 391 | end 392 | 393 | childAPI = c.setChild(class); 394 | return childAPI; 395 | end 396 | 397 | function class.envAcquireDispatcher(prev) 398 | if (prev == methodenv) then return prev; end; 399 | 400 | local meta = {}; 401 | 402 | function meta.__index(t, k) 403 | local val = prev[k]; 404 | 405 | if not (val == nil) then 406 | return val; 407 | end 408 | 409 | return methodenv[k]; 410 | end 411 | 412 | meta.__newindex = methodenv; 413 | 414 | local newenv = {}; 415 | setmetatable(newenv, meta); 416 | 417 | return newenv; 418 | end 419 | 420 | function class.getChildAPI() 421 | return childAPI; 422 | end 423 | 424 | function class.isDestroying() 425 | return destroying; 426 | end 427 | 428 | function class.getChildren() 429 | return children; 430 | end 431 | 432 | class.children = children; 433 | 434 | -- Every class is destroyable 435 | function class.destroy() 436 | methods.__metatable = nil; 437 | 438 | setmetatable(class, {}); 439 | 440 | methods = nil; 441 | children = nil; 442 | lastDestroy = nil; 443 | forceSuperHandlers = nil; 444 | methodenv = nil; 445 | meta = nil; 446 | classmeta = nil; 447 | class = nil; 448 | end 449 | 450 | return class, methodenv; 451 | end 452 | _G.createClass = createClass; -------------------------------------------------------------------------------- /gfriend.dff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quiret/mta_lua_3d_math/5e6b7cc00b62a3f3002876773b0312219571d639/gfriend.dff -------------------------------------------------------------------------------- /gfriend.txd: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quiret/mta_lua_3d_math/5e6b7cc00b62a3f3002876773b0312219571d639/gfriend.txd -------------------------------------------------------------------------------- /math_client.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | local server_texture = false; 3 | 4 | addEvent("onServerTransmitImage", true); 5 | addEventHandler("onServerTransmitImage", root, function(imgPixels) 6 | if ( server_texture ) then 7 | destroyElement(server_texture); 8 | end 9 | 10 | server_texture = dxCreateTexture(imgPixels, "argb", false, "wrap"); 11 | end 12 | ); 13 | 14 | addEventHandler( "onClientRender", root, function() 15 | -- draw a server transmitted image. 16 | if ( server_texture ) then 17 | local width, height = dxGetMaterialSize( server_texture ); 18 | dxDrawImage( 0, 0, width, height, server_texture ); 19 | end 20 | end 21 | ); 22 | 23 | addCommandHandler("bbcl", function() 24 | if (server_texture) then 25 | destroyElement(server_texture); 26 | 27 | server_texture = false; 28 | end 29 | end 30 | ); -------------------------------------------------------------------------------- /math_condchains_linopt.lua: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/quiret/mta_lua_3d_math/5e6b7cc00b62a3f3002876773b0312219571d639/math_condchains_linopt.lua -------------------------------------------------------------------------------- /math_condtransform.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations. 2 | local ipairs = ipairs; 3 | local table = table; 4 | local tinsert = table.insert; 5 | local error = error; 6 | 7 | -- Global imports. 8 | local travel_and_line = travel_and_line; 9 | local for_all_cases = for_all_cases; 10 | local createConditionOR = createConditionOR; 11 | local createConditionAND = createConditionAND; 12 | local output_math_debug = output_math_debug; 13 | local resolve_cond = resolve_cond; 14 | local _math_eq = _math_eq; 15 | local _math_geq = _math_geq; 16 | local _math_leq = _math_leq; 17 | 18 | if not (travel_and_line) or not (for_all_cases) or not (createConditionOR) or 19 | not (createConditionAND) or not (output_math_debug) or 20 | not (resolve_cond) or not (_math_eq) or not (_math_geq) or 21 | not (_math_leq) then 22 | 23 | error("failed global import of dependencies; fatal script error."); 24 | end 25 | 26 | local function disect_conditions(container, resconts, othercond, disectors) 27 | travel_and_line(container, 28 | function(n) 29 | local solutionType = n.getSolutionType(); 30 | 31 | local was_added = false; 32 | 33 | for checkidx,checkcb in ipairs(disectors) do 34 | if (checkcb(solutionType)) then 35 | tinsert(resconts[checkidx], resolve_cond(n)); 36 | 37 | was_added = true; 38 | end 39 | end 40 | 41 | if not (was_added) and (othercond) then 42 | othercond.establishAND(resolve_cond(n)); 43 | end 44 | end 45 | ); 46 | end 47 | _G.disect_conditions = disect_conditions; 48 | 49 | local function filter_conditions(container, resconts, othercond, obj_disectors) 50 | travel_and_line(container, 51 | function(n) 52 | local was_added = false; 53 | 54 | for checkidx,checkcb in ipairs(obj_disectors) do 55 | if (checkcb(n)) then 56 | tinsert(resconts[checkidx], resolve_cond(n)); 57 | 58 | was_added = true; 59 | end 60 | end 61 | 62 | if not (was_added) and (othercond) then 63 | othercond.establishAND(resolve_cond(n)); 64 | end 65 | end 66 | ); 67 | end 68 | _G.filter_conditions = filter_conditions; 69 | 70 | local function has_condchain(cond) 71 | -- Determine if cond is actually a condchain (not what type of chain). 72 | local solutionType = cond.getSolutionType(); 73 | 74 | return (solutionType == "and-dynamic") or (solutionType == "or-dynamic"); 75 | end 76 | _G.has_condchain = has_condchain; 77 | 78 | local for_all_andChains = nil; 79 | 80 | function for_all_andChains(condchain, cb, parent, parentIdx) 81 | local ourSolutionType = condchain.getSolutionType(); 82 | 83 | local trav_children = false; 84 | 85 | local ret_strat = nil; 86 | 87 | if (ourSolutionType == "or-dynamic") then 88 | -- Just treat all the sub cases. 89 | trav_children = true; 90 | condchain = resolve_cond( condchain ); 91 | elseif (ourSolutionType == "and-dynamic") then 92 | condchain = resolve_cond( condchain ); 93 | 94 | local travType, userdata = cb(condchain, parent, parentIdx); 95 | 96 | if (travType == "continue") then 97 | trav_children = true; 98 | elseif (travType == "update-current") or (travType == "update-current-and-continue") then 99 | condchain = resolve_cond( userdata ); 100 | 101 | if (parent) then 102 | parent.replaceVar(parentIdx, nil, condchain); 103 | ret_strat = "parent-update"; 104 | end 105 | 106 | if (travType == "update-current-and-continue") then 107 | trav_children = true; 108 | end 109 | end 110 | elseif (ourSolutionType == "boolean") then 111 | -- Just shorten out. 112 | return condchain; 113 | else 114 | math_assert( false, "invalid reduction chain type: " .. ourSolutionType .. " (" .. condchain.toString() .. ")", 2 ); 115 | end 116 | 117 | if (trav_children) and (has_condchain(condchain)) then 118 | local idx = 1; 119 | local vars = condchain.getVars(); 120 | 121 | while (idx <= #vars) do 122 | local var = vars[idx]; 123 | local advance = true; 124 | 125 | if (has_condchain(var)) then 126 | local newVal, strat = for_all_andChains(var, cb, condchain, idx); 127 | 128 | if (strat == "parent-update") then 129 | if (parent) then 130 | parent.replaceVar(parentIdx, nil, condchain); 131 | end 132 | 133 | ret_strat = "parent-update"; 134 | 135 | if (condchain.getChainType() == "none") then 136 | condchain = resolve_cond(condchain); 137 | break; 138 | end 139 | end 140 | 141 | if (newVal.getSolutionType() == "boolean") then 142 | advance = false; 143 | end 144 | end 145 | 146 | if (advance) then 147 | idx = idx + 1; 148 | end 149 | end 150 | end 151 | 152 | return condchain, ret_strat; 153 | end 154 | _G.for_all_andChains = for_all_andChains; 155 | 156 | local function is_condobj_solvewise_same(left, right) 157 | if not (left.getSolutionType() == right.getSolutionType()) then 158 | return false; 159 | end 160 | 161 | local left_solve = { left.solve() }; 162 | local right_solve = { right.solve() }; 163 | 164 | if not (#left_solve == #right_solve) then 165 | return false; 166 | end 167 | 168 | for m,n in ipairs(left_solve) do 169 | if not (_math_eq(n, right_solve[m])) then 170 | return false; 171 | end 172 | end 173 | 174 | return true; 175 | end 176 | 177 | local function calculateDisambiguationCondition(condlist, doPrintDebug) 178 | local disambs = {}; 179 | 180 | for infsupidx,infsup in ipairs(condlist) do 181 | local has_left_double = false; 182 | 183 | for checkidx=1,infsupidx-1 do 184 | local checkitem = condlist[checkidx]; 185 | 186 | if (is_condobj_solvewise_same(infsup, checkitem)) then 187 | has_left_double = true; 188 | end 189 | end 190 | 191 | if not (has_left_double) then 192 | local andItem = {}; 193 | andItem.infsup = infsup; 194 | andItem.cond = createConditionAND(true); 195 | tinsert(disambs, andItem); 196 | else 197 | if (doPrintDebug) then 198 | output_math_debug( "terminated solvewise-double of " .. infsup.toString() ); 199 | end 200 | end 201 | end 202 | 203 | if (#disambs > 1) then 204 | for _,infsup_and in ipairs(disambs) do 205 | local infsup = infsup_and.infsup; 206 | 207 | if (doPrintDebug) then 208 | output_math_debug( "CTX: " .. infsup.toString() ); 209 | end 210 | 211 | for _,othercond_and in ipairs(disambs) do 212 | if not (infsup_and == othercond_and) then 213 | local othercond = othercond_and.infsup; 214 | local cond = infsup.disambiguate(othercond, doPrintDebug); 215 | 216 | if (doPrintDebug) then 217 | output_math_debug( cond.toString() ); 218 | end 219 | 220 | infsup_and.cond.addVar(cond); 221 | end 222 | end 223 | end 224 | end 225 | 226 | return disambs; 227 | end 228 | _G.calculateDisambiguationCondition = calculateDisambiguationCondition; 229 | 230 | local function disambiguateBoundaryConditions(_condchain_try, is_lower_bound, is_upper_bound, hint_text, do_full_objcheck) 231 | return for_all_andChains(_condchain_try, 232 | function(condchain, parent, parentIdx) 233 | local lowers = {}; 234 | local uppers = {}; 235 | local otherconds = createConditionAND(true); 236 | 237 | if (do_full_objcheck) then 238 | filter_conditions( condchain, { lowers, uppers }, otherconds, { is_lower_bound, is_upper_bound } ); 239 | else 240 | disect_conditions( condchain, { lowers, uppers }, otherconds, { is_lower_bound, is_upper_bound } ); 241 | end 242 | 243 | if (#lowers > 1) or (#uppers > 1) then 244 | -- First simplify any other conditions that are below us. 245 | if (has_condchain(otherconds)) then 246 | otherconds = disambiguateBoundaryConditions(otherconds, is_lower_bound, is_upper_bound, hint_text, do_full_objcheck); 247 | end 248 | 249 | if (hint_text) then 250 | output_math_debug( "TARGET: " .. condchain.toString() ); 251 | end 252 | 253 | local lowers_cond = false; 254 | 255 | local function debug_disamb_createCond(disamb) 256 | local orCond = createConditionOR(); 257 | 258 | for m,n in ipairs(disamb) do 259 | local andItem = createConditionAND(); 260 | andItem.addVar(n.infsup); 261 | andItem.establishAND(n.cond); 262 | orCond.addVar(andItem); 263 | end 264 | 265 | return orCond; 266 | end 267 | 268 | if (#lowers > 0) then 269 | if (hint_text) and (#lowers > 1) then 270 | output_math_debug( "CALCULATING " .. hint_text .. " LOWER BOUNDARIES (" .. #lowers .. "):" ); 271 | end 272 | 273 | lowers_cond = calculateDisambiguationCondition( lowers, hint_text ); 274 | 275 | if (hint_text) and (#lowers > 1) then 276 | output_math_debug( "RESULT: " .. debug_disamb_createCond(lowers_cond).toString() ); 277 | end 278 | end 279 | 280 | local uppers_cond = false; 281 | 282 | if (#uppers > 0) then 283 | if (hint_text) and (#uppers > 1) then 284 | output_math_debug( "CALCULATING " .. hint_text .. " UPPER BOUNDARIES (" .. #uppers .. "):" ); 285 | end 286 | 287 | uppers_cond = calculateDisambiguationCondition( uppers, hint_text ); 288 | 289 | if (hint_text) and (#uppers > 1) then 290 | output_math_debug( "RESULT: " .. debug_disamb_createCond(uppers_cond).toString() ); 291 | end 292 | end 293 | 294 | -- TODO: fix condition chain assignment by cloning the condition chain if it is inside a condition tree (and not the root). 295 | -- then replace the item instead of resetting the existing node (we pass a result). 296 | 297 | if (parent) then 298 | condchain = createConditionAND(); 299 | else 300 | condchain.reset(); 301 | end 302 | 303 | if (lowers_cond) and (uppers_cond) then 304 | if (hint_text) then 305 | output_math_debug( "COMBINING LOWERS AND UPPERS:" ); 306 | end 307 | 308 | local combinedCond = createConditionOR(); 309 | 310 | for _,lower_info in ipairs(lowers_cond) do 311 | for _,upper_info in ipairs(uppers_cond) do 312 | local combItem = createConditionAND(); 313 | combItem.addVar(lower_info.infsup); 314 | combItem.addVar(upper_info.infsup); 315 | combItem.addVar(lower_info.cond); 316 | combItem.addVar(upper_info.cond); 317 | 318 | if not (combItem.getSolutionType() == "boolean") then 319 | local valCond = lower_info.infsup.calcValidityUpper(upper_info.infsup, hint_text); 320 | 321 | if (hint_text) then 322 | output_math_debug( valCond.toString() ); 323 | end 324 | 325 | combItem.addVar(valCond); 326 | end 327 | 328 | combinedCond.addVar(combItem); 329 | end 330 | end 331 | 332 | if (hint_text) then 333 | output_math_debug( "COMBINATION RESULT: " .. combinedCond.toString() ); 334 | end 335 | 336 | condchain.establishAND( combinedCond ); 337 | else 338 | local function subject_normal_disambSwitch(cond) 339 | local orChain = createConditionOR(); 340 | 341 | for m,n in ipairs(cond) do 342 | n.cond.addVar(n.infsup); 343 | orChain.addVar(n.cond); 344 | end 345 | 346 | return orChain; 347 | end 348 | 349 | if (lowers_cond) then 350 | condchain.addVar( subject_normal_disambSwitch( lowers_cond ) ); 351 | end 352 | 353 | if (uppers_cond) then 354 | condchain.addVar( subject_normal_disambSwitch( uppers_cond ) ); 355 | end 356 | 357 | if (hint_text) then 358 | output_math_debug( "TO BE INSERTED: " .. condchain.toString() ); 359 | end 360 | end 361 | 362 | condchain.establishAND(otherconds); 363 | 364 | return "update-current", condchain; 365 | else 366 | return "continue"; 367 | end 368 | end 369 | ); 370 | end 371 | _G.disambiguateBoundaryConditions = disambiguateBoundaryConditions; 372 | 373 | local function reduceConditions(condchain, is_reducible_cb, reduce_by, doPrintDebug, parent, parentIdx) 374 | -- TODO: tighten up the solidity of this runtime by actually going about the way it traverses the tree. 375 | 376 | condchain = resolve_cond(condchain); 377 | 378 | local condvars = condchain.getVars(); 379 | 380 | local n = 1; 381 | 382 | local ret_strat = nil; 383 | 384 | local has_unique_chain = false; 385 | 386 | while ( n <= #condvars ) do 387 | local var = condvars[n]; 388 | 389 | local solutionType = var.getSolutionType(); 390 | 391 | if (solutionType == "and-dynamic") or (solutionType == "or-dynamic") then 392 | var = resolve_cond(var); 393 | 394 | local strat; 395 | var, strat = reduceConditions(var, is_reducible_cb, reduce_by, doPrintDebug, condchain, n); 396 | 397 | if (strat == "parent-update") then 398 | if (parent) then 399 | parent.replaceVar(parentIdx, nil, condchain); 400 | end 401 | 402 | ret_strat = "parent-update"; 403 | 404 | if (condchain.getChainType() == "none") then 405 | break; 406 | end 407 | end 408 | 409 | solutionType = var.getSolutionType(); 410 | end 411 | 412 | local advance = true; 413 | 414 | var = resolve_cond(var); 415 | 416 | if (solutionType == "boolean") then 417 | advance = false; 418 | elseif not (var == reduce_by) and (is_reducible_cb(solutionType)) then 419 | local reduced = reduce_by.disambiguate(var, doPrintDebug); 420 | 421 | if (doPrintDebug) then 422 | output_math_debug( reduced.toString() ); 423 | end 424 | 425 | if (parent) and not (has_unique_chain) then 426 | condchain = condchain.clone(); 427 | condvars = condchain.getVars(); 428 | 429 | has_unique_chain = true; 430 | end 431 | 432 | local resReplace = condchain.replaceVar( n, nil, reduced ); 433 | 434 | if (parent) then 435 | parent.replaceVar( parentIdx, nil, condchain ); 436 | ret_strat = "parent-update"; 437 | end 438 | 439 | if (condchain.getChainType() == "none") then 440 | if (doPrintDebug) then 441 | output_math_debug( "shortened out because simplification" ); 442 | end 443 | 444 | break; 445 | end 446 | 447 | advance = ( resReplace == "ok" ); 448 | end 449 | 450 | if ( advance ) then 451 | n = n + 1; 452 | end 453 | end 454 | 455 | return condchain, ret_strat; 456 | end 457 | 458 | local function simplify_by_distinguished_condition(_condchain_try, is_cond_distinguished, is_cond_reducible, doPrintDebug) 459 | return for_all_andChains(_condchain_try, 460 | function(condchain, parent, parentIdx) 461 | local solution = false; 462 | local num_solutions = 0; 463 | 464 | travel_and_line(condchain, 465 | function(conditem) 466 | local solutionType = conditem.getSolutionType(); 467 | 468 | if (is_cond_distinguished(solutionType)) then 469 | if not (solution) then 470 | solution = resolve_cond( conditem ); 471 | end 472 | 473 | num_solutions = num_solutions + 1; 474 | end 475 | end 476 | ); 477 | 478 | if (solution) then 479 | if (doPrintDebug) then 480 | output_math_debug( "found " .. num_solutions .. " distinguished conditions" ); 481 | output_math_debug( "TARGET: " .. condchain.toString() ); 482 | end 483 | 484 | if (condchain.getSolutionType() == "and-dynamic") then 485 | if (doPrintDebug) then 486 | output_math_debug( "simplifying conditions by distinguished solution" ); 487 | end 488 | 489 | local strat; 490 | condchain, strat = reduceConditions( condchain, is_cond_reducible, solution, doPrintDebug ); 491 | 492 | if (doPrintDebug) then 493 | output_math_debug( "simplified result: " .. condchain.toString() ); 494 | end 495 | else 496 | if (doPrintDebug) then 497 | output_math_debug( "shortened out because boolean-state" ); 498 | end 499 | end 500 | 501 | return "update-current", condchain; 502 | else 503 | return "continue"; 504 | end 505 | end 506 | ); 507 | end 508 | _G.simplify_by_distinguished_condition = simplify_by_distinguished_condition; 509 | 510 | local function smart_group_condition(cond, is_relevant_in_orCond, is_relevant_planarCond, hint_text) 511 | -- TODO: for each and-dynamic disect into relevant conditions (is_lower_bound, is_upper_bound combined) and 512 | -- group them into a shared condition bubble; then look into this group to find the last and most-caseful or-dynamic. 513 | -- Execute establishAND on said last and most-caseful or-dynamic. If executed from the deepest 514 | -- or-dynamic leaves then all relevant or-dynamic items in the tree are fully laid out. 515 | return for_all_andChains(cond, 516 | function(condchain, parent, parentIdx) 517 | local orConds = {}; 518 | local relevantConds = {}; 519 | local otherconds = createConditionAND(true); 520 | 521 | local function is_relevant_condobj(subcond) 522 | return is_relevant_planarCond(subcond.getSolutionType()); 523 | end 524 | 525 | local function is_relevant_or_dynamic(subcond) 526 | if not (subcond.getSolutionType() == "or-dynamic") then return false; end; 527 | 528 | subcond = resolve_cond(subcond); 529 | 530 | local is_whole_relevant = false; 531 | 532 | for_all_cases(subcond, 533 | function(case) 534 | travel_and_line(case, 535 | function(caseitem) 536 | local subSolutionType = caseitem.getSolutionType(); 537 | 538 | local is_relevant_case = false; 539 | 540 | if (subSolutionType == "or-dynamic") then 541 | is_relevant_case = is_relevant_or_dynamic(caseitem); 542 | else 543 | is_relevant_case = is_relevant_in_orCond(subSolutionType); 544 | end 545 | 546 | if (is_relevant_case) then 547 | is_whole_relevant = true; 548 | end 549 | end 550 | ); 551 | end 552 | ); 553 | 554 | return is_whole_relevant; 555 | end 556 | 557 | filter_conditions(condchain, { orConds, relevantConds }, otherconds, { is_relevant_or_dynamic, is_relevant_condobj } ); 558 | 559 | -- TODO: properly handle the local calculation result by pushing it down. 560 | 561 | if ( #orConds >= 1 ) then 562 | -- First we solve the problem for all sub-or-chains. 563 | -- NOTE that we group AND solve, while in the original idea we just grouped on sub-chains 564 | -- and did a complete solve on the resulting normal layout. It stands to be decided 565 | -- what the better approach is. 566 | 567 | -- Add all the normal conditions. 568 | local first_rel_orCond = orConds[1].clone(); 569 | 570 | for m,n in ipairs(relevantConds) do 571 | first_rel_orCond.establishAND(n); 572 | end 573 | 574 | orConds[1] = first_rel_orCond; 575 | 576 | for m,subcases in ipairs(orConds) do 577 | orConds[m] = resolve_cond( smart_group_condition(subcases, is_relevant_in_orCond, is_relevant_planarCond, hint_text) ); 578 | end 579 | 580 | if (hint_text) and (#orConds > 1) then 581 | output_math_debug( "found multiple relevant cases in or-dynamic; smart-grouping..." ); 582 | output_math_debug( "TARGET: " .. condchain.toString() ); 583 | end 584 | 585 | -- Now we assume that each child or-dynamic is layed out fully, by relevance. 586 | local most_caseful_and_last = false; 587 | local case_count; 588 | 589 | for m,n in ipairs(orConds) do 590 | local vars = n.getVars(); 591 | 592 | if (most_caseful_and_last == false) or (case_count <= #vars) then 593 | most_caseful_and_last = n; 594 | case_count = #vars; 595 | end 596 | end 597 | 598 | -- Execute establishAND on it. 599 | local target_cond = most_caseful_and_last.clone(); 600 | 601 | for m,n in ipairs(orConds) do 602 | if not (n == most_caseful_and_last) then 603 | if (hint_text) then 604 | output_math_debug( "conjuncting [ " .. n.toString() .. " ] with [ " .. most_caseful_and_last.toString() .. " ]" ); 605 | end 606 | target_cond.distributeAND(n); 607 | end 608 | end 609 | 610 | if (hint_text) and (#orConds > 1) then 611 | output_math_debug( "conjunction result: " .. target_cond.toString() ); 612 | end 613 | 614 | -- Make the replacement condchain. 615 | if not (parent) then 616 | condchain.reset(); 617 | else 618 | condchain = createConditionAND(); 619 | end 620 | 621 | condchain.addVar(otherconds); 622 | condchain.establishAND(target_cond); 623 | 624 | if (hint_text) then 625 | output_math_debug( "RESULT: " .. condchain.toString() ); 626 | end 627 | 628 | return "update-current", condchain; 629 | else 630 | return "next"; 631 | end 632 | end 633 | ); 634 | end 635 | _G.smart_group_condition = smart_group_condition; 636 | 637 | local function layout_all_cases(_condchain_try) 638 | return for_all_andChains(_condchain_try, 639 | function(condchain, parent, parentIdx) 640 | local orConds = {}; 641 | local otherconds = createConditionAND(true); 642 | 643 | local function is_or_dynamic(solutionType) 644 | return ( solutionType == "or-dynamic" ); 645 | end 646 | 647 | disect_conditions(condchain, { orConds }, otherconds, { is_or_dynamic } ); 648 | 649 | if ( #orConds >= 1 ) then 650 | -- First we update all child or-dynamics. 651 | for _,orChild in ipairs(orConds) do 652 | for m,n in ipairs(orChild.getVars()) do 653 | if (has_condchain(n)) then 654 | local layed_out = layout_all_cases(n); 655 | orChild.replaceVar(m, nil, layed_out); 656 | end 657 | end 658 | end 659 | 660 | -- We just want to clobber together. 661 | target_or = orConds[1].clone(); 662 | 663 | for m=2,#orConds do 664 | target_or.distributeAND(orConds[m]); 665 | end 666 | 667 | target_or.distributeAND(otherconds); 668 | 669 | return "update-current", target_or; 670 | else 671 | return "next"; 672 | end 673 | end 674 | ); 675 | end 676 | _G.layout_all_cases = layout_all_cases; -------------------------------------------------------------------------------- /math_linearconds_2d.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations. 2 | local tostring = tostring; 3 | local _G = _G; 4 | local error = error; 5 | 6 | -- Global imports. 7 | local _math_eq = _math_eq; 8 | local _math_geq = _math_geq; 9 | local _math_leq = _math_leq; 10 | local output_math_debug = output_math_debug; 11 | local createConditionBoolean = createConditionBoolean; 12 | local mcs_multsum = mcs_multsum; 13 | 14 | if not (_math_eq) or not (_math_geq) or not (_math_leq) or 15 | not (output_math_debug) or not (createConditionBoolean) or 16 | not (mcs_multsum) then 17 | 18 | error("cannot import global function; fatal script error."); 19 | end 20 | 21 | -- 0 = a1*k1 + b1*k2 + k3 22 | local function createCondition2DLinearEquality(k1, k2, k3) 23 | -- Pre-calculate the conditional object. 24 | local c1, c2; 25 | local solutionType; 26 | 27 | if not (_math_eq(k1, 0)) then 28 | solutionType = "a1equal"; 29 | c1 = (-k2)/k1; 30 | c2 = (-k3)/k1; 31 | elseif not (_math_eq(k2, 0)) then 32 | solutionType = "b1equal"; 33 | c1 = (-k3)/k2; 34 | else 35 | return createConditionBoolean( _math_eq(k3, 0) ); 36 | end 37 | 38 | local cond = {}; 39 | 40 | function cond.getSolutionType() 41 | return solutionType; 42 | end 43 | 44 | function cond.solve() 45 | if (solutionType == "a1equal") then 46 | -- a1 = c1*b1 + c2 47 | return c1, c2; 48 | elseif (solutionType == "b1equal") then 49 | -- b1 = c1 50 | return c1; 51 | end 52 | end 53 | 54 | function cond.toStringEQ() 55 | if (solutionType == "a1equal") then 56 | return mcs_multsum({c1, c2}, {"b1"}); 57 | elseif (solutionType == "b1equal") then 58 | return tostring( c1 ); 59 | end 60 | 61 | return "unknown"; 62 | end 63 | 64 | function cond.toString() 65 | if (solutionType == "a1equal") then 66 | return "a1 = " .. cond.toStringEQ(); 67 | elseif (solutionType == "b1equal") then 68 | return "b1 = " .. cond.toStringEQ(); 69 | end 70 | 71 | return "unknown"; 72 | end 73 | 74 | function cond.disambiguate(otherCond, doPrintDebug) 75 | local ourSolutionType = cond.getSolutionType(); 76 | local otherSolutionType = otherCond.getSolutionType(); 77 | 78 | if (ourSolutionType == "a1equal") then 79 | if (otherSolutionType == "a1min") then 80 | local k1b, k2b = cond.solve(); 81 | local k1l, k2l = otherCond.solve(); 82 | 83 | if (doPrintDebug) then 84 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 85 | end 86 | 87 | return createCondition2DLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 88 | elseif (otherSolutionType == "a1max") then 89 | local k1l, k2l = cond.solve(); 90 | local k1b, k2b = otherCond.solve(); 91 | 92 | if (doPrintDebug) then 93 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 94 | end 95 | 96 | return createCondition2DLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 97 | elseif (otherSolutionType == "a1inferior") then 98 | local k1rb, k2rb = cond.solve(); 99 | local k1rl, k2rl = otherCond.solve(); 100 | 101 | if (doPrintDebug) then 102 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 103 | end 104 | 105 | return createCondition2DLinearInequalityLTEQ( 0, k1rb - k1rl, k2rb - k2rl ); 106 | elseif (otherSolutionType == "a1superior") then 107 | local k1rl, k2rl = cond.solve(); 108 | local k1rb, k2rb = otherCond.solve(); 109 | 110 | if (doPrintDebug) then 111 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 112 | end 113 | 114 | return createCondition2DLinearInequalityLTEQ( 0, k1rb - k1rl, k2rb - k2rl ); 115 | elseif (otherSolutionType == "a1equal") then 116 | local k1a, k2a = cond.solve(); 117 | local k1b, k2b = otherCond.solve(); 118 | 119 | if (doPrintDebug) then 120 | output_math_debug( "REDUCE: " .. cond.toStringEQ() .. " = " .. otherCond.toStringEQ() ); 121 | end 122 | 123 | return createCondition2DLinearEquality( 0, k1a - k1b, k2a - k2b ); 124 | end 125 | elseif (ourSolutionType == "b1equal") then 126 | if (otherSolutionType == "b1min") then 127 | local k1b = cond.solve(); 128 | local k1l = otherCond.solve(); 129 | 130 | if (doPrintDebug) then 131 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 132 | end 133 | 134 | return createConditionBoolean( _math_geq(k1b, k1l) ); 135 | elseif (otherSolutionType == "b1max") then 136 | local k1l = cond.solve(); 137 | local k1b = otherCond.solve(); 138 | 139 | if (doPrintDebug) then 140 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 141 | end 142 | 143 | return createConditionBoolean( _math_leq(k1l, k1b) ); 144 | elseif (otherSolutionType == "b1inferior") then 145 | local k1rb = cond.solve(); 146 | local k1rl = otherCond.solve(); 147 | 148 | if (doPrintDebug) then 149 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 150 | end 151 | 152 | return createConditionBoolean(k1rb > k1rl); 153 | elseif (otherSolutionType == "b1superior") then 154 | local k1rl = cond.solve(); 155 | local k1rb = otherCond.solve(); 156 | 157 | if (doPrintDebug) then 158 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 159 | end 160 | 161 | return createConditionBoolean(k1rl < k1rb); 162 | elseif (otherSolutionType == "b1equal") then 163 | local k1a = cond.solve(); 164 | local k1b = otherCond.solve(); 165 | 166 | if (doPrintDebug) then 167 | output_math_debug( "REDUCE: " .. cond.toStringEQ() .. " = " .. otherCond.toStringEQ() ); 168 | end 169 | 170 | return createConditionBoolean(_math_eq(k1a, k1b)); 171 | end 172 | end 173 | 174 | math_assert( false, "DISAMB ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 175 | 176 | return false; 177 | end 178 | 179 | return cond; 180 | end 181 | _G.createCondition2DLinearEquality = createCondition2DLinearEquality; 182 | 183 | -- 0 != a1*k1 + b1*k2 + k3 184 | function createCondition2DLinearInequalityNEQ(k1, k2, k3) 185 | -- Pre-calculate the condition. 186 | local c1, c2; 187 | local solutionType; 188 | 189 | if not (_math_eq(k1, 0)) then 190 | solutionType = "a1nequal"; 191 | c1 = (-k2)/k1; 192 | c2 = (-k3)/k1; 193 | elseif not (_math_eq(k2, 0)) then 194 | solutionType = "b1nequal"; 195 | c1 = (-k3)/k2; 196 | else 197 | return createConditionBoolean( not _math_eq(k3, 0) ); 198 | end 199 | 200 | local cond = {}; 201 | 202 | function cond.getSolutionType() 203 | return solutionType; 204 | end 205 | 206 | function cond.solve() 207 | if (solutionType == "a1nequal") then 208 | -- a1 != b1*c1 + c2 209 | return c1, c2; 210 | elseif (solutionType == "b1nequal") then 211 | -- b1 != c1 212 | return c1; 213 | end 214 | end 215 | 216 | function cond.toStringEQ() 217 | if (solutionType == "a1nequal") then 218 | return mcs_multsum({c1, c2}, {"b1"}); 219 | elseif (solutionType == "b1nequal") then 220 | return tostring( c1 ); 221 | end 222 | 223 | return "unknown"; 224 | end 225 | 226 | function cond.toString() 227 | if (solutionType == "a1nequal") then 228 | return "a1 != " .. cond.toStringEQ(); 229 | elseif (solutionType == "b1nequal") then 230 | return "b1 != " .. cond.toStringEQ(); 231 | end 232 | 233 | return "unknown"; 234 | end 235 | 236 | function cond.disambiguate(otherCond, doPrintDebug) 237 | if (doPrintDebug) then 238 | output_math_debug( "not meant to disambiguate a non-equal condition" ); 239 | end 240 | 241 | return false; 242 | end 243 | 244 | return cond; 245 | end 246 | _G.createCondition2DLinearInequalityNEQ = createCondition2DLinearInequalityNEQ; 247 | 248 | local function sharedCalculateValidityUpper2DInequality(cond, otherCond, doPrintDebug) 249 | local ourSolutionType = cond.getSolutionType(); 250 | local otherSolutionType = otherCond.getSolutionType(); 251 | 252 | if (ourSolutionType == "b1min") and (otherSolutionType == "b1superior") or 253 | (ourSolutionType == "b1inferior") and (otherSolutionType == "b1max") or 254 | (ourSolutionType == "b1inferior") and (otherSolutionType == "b1superior") then 255 | 256 | local lk1 = cond.solve(); 257 | local uk1 = otherCond.solve(); 258 | 259 | if (doPrintDebug) or (cfg_output_trivial_calc) then 260 | output_math_debug( "CALCVAL: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 261 | end 262 | 263 | return createConditionBoolean( lk1 < uk1 ); 264 | elseif (ourSolutionType == "b1min") and (otherSolutionType == "b1max") then 265 | local mink1 = cond.solve(); 266 | local maxk1 = otherCond.solve(); 267 | 268 | if (doPrintDebug) or (cfg_output_trivial_calc) then 269 | output_math_debug( "CALCVAL: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 270 | end 271 | 272 | return createConditionBoolean( _math_leq(mink1, maxk1) ); 273 | elseif (ourSolutionType == "a1min") and (otherSolutionType == "a1superior") or 274 | (ourSolutionType == "a1inferior") and (otherSolutionType == "a1max") or 275 | (ourSolutionType == "a1inferior") and (otherSolutionType == "a1superior") then 276 | 277 | local lk1, lk2 = cond.solve(); 278 | local uk1, uk2 = otherCond.solve(); 279 | 280 | if (doPrintDebug) then 281 | output_math_debug( "CALCVAL: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 282 | end 283 | 284 | return createCondition2DLinearInequalityLT( 0, uk1 - lk1, uk2 - lk2 ); 285 | elseif (ourSolutionType == "a1min") and (otherSolutionType == "a1max") then 286 | local mink1, mink2 = cond.solve(); 287 | local maxk1, maxk2 = otherCond.solve(); 288 | 289 | if (doPrintDebug) then 290 | output_math_debug( "CALCVAL: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 291 | end 292 | 293 | return createCondition2DLinearInequalityLTEQ( 0, maxk1 - mink1, maxk2 - mink2 ); 294 | end 295 | 296 | math_assert( false, "CALCVAL ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 297 | 298 | return false; 299 | end 300 | 301 | -- 0 < a1*k1 + b1*k2 + k3 302 | local function createCondition2DLinearInequalityLT(k1, k2, k3) 303 | -- Pre-calculate the condition. 304 | local solutionType; 305 | local is_a1_bound = false; 306 | local is_b1_bound = false; 307 | local c1, c2; 308 | 309 | if not (_math_eq(k1, 0)) then 310 | if (k1 > 0) then 311 | solutionType = "a1inferior"; 312 | else 313 | solutionType = "a1superior"; 314 | end 315 | 316 | is_a1_bound = true; 317 | c1 = (-k2)/k1; 318 | c2 = (-k3)/k1; 319 | elseif not (_math_eq(k2, 0)) then 320 | if (k2 > 0) then 321 | solutionType = "b1inferior"; 322 | else 323 | solutionType = "b1superior"; 324 | end 325 | 326 | is_b1_bound = true; 327 | c1 = (-k3)/k2; 328 | else 329 | return createConditionBoolean( 0 < k3 ); 330 | end 331 | 332 | local cond = {}; 333 | 334 | function cond.getSolutionType() 335 | return solutionType; 336 | end 337 | 338 | function cond.solve() 339 | if (is_a1_bound) then 340 | -- a1 c1*b1 + c2 341 | return c1, c2; 342 | elseif (is_b1_bound) then 343 | -- b1 c1 344 | return c1; 345 | end 346 | end 347 | 348 | function cond.toStringEQ() 349 | if (solutionType == "a1superior") or (solutionType == "a1inferior") then 350 | return mcs_multsum({c1, c2}, {"b1"}); 351 | elseif (solutionType == "b1superior") or (solutionType == "b1inferior") then 352 | return tostring( c1 ); 353 | end 354 | 355 | return "unknown"; 356 | end 357 | 358 | function cond.toString() 359 | if (solutionType == "a1superior") then 360 | return "a1 < " .. cond.toStringEQ(); 361 | elseif (solutionType == "a1inferior") then 362 | return "a1 > " .. cond.toStringEQ(); 363 | elseif (solutionType == "b1superior") then 364 | return "b1 < " .. cond.toStringEQ(); 365 | elseif (solutionType == "b1inferior") then 366 | return "b1 > " .. cond.toStringEQ(); 367 | end 368 | 369 | return "unknown"; 370 | end 371 | 372 | function cond.disambiguate(otherCond, doPrintDebug) 373 | local ourSolutionType = cond.getSolutionType(); 374 | local otherSolutionType = otherCond.getSolutionType(); 375 | 376 | if (ourSolutionType == "a1inferior") then 377 | if (otherSolutionType == "a1inferior") or (otherSolutionType == "a1min") then 378 | local k1b, k2b = cond.solve(); 379 | local k1l, k2l = otherCond.solve(); 380 | 381 | if (doPrintDebug) then 382 | output_math_debug( "SOLINF: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 383 | end 384 | 385 | return createCondition2DLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 386 | elseif (otherSolutionType == "a1equal") then 387 | return createConditionBoolean(false); 388 | end 389 | elseif (ourSolutionType == "a1superior") then 390 | if (otherSolutionType == "a1superior") or (otherSolutionType == "a1max") then 391 | local k1l, k2l = cond.solve(); 392 | local k1b, k2b = otherCond.solve(); 393 | 394 | if (doPrintDebug) then 395 | output_math_debug( "SOLSUP: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 396 | end 397 | 398 | return createCondition2DLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 399 | elseif (otherSolutionType == "a1equal") then 400 | return createConditionBoolean(false); 401 | end 402 | elseif (ourSolutionType == "b1inferior") then 403 | if (otherSolutionType == "b1inferior") or (otherSolutionType == "b1min") then 404 | local k1b = cond.solve(); 405 | local k1l = otherCond.solve(); 406 | 407 | if (doPrintDebug) and (cfg_output_trivial_calc) then 408 | output_math_debug( "INFCHECK: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 409 | end 410 | 411 | return createConditionBoolean( _math_geq(k1b, k1l) ); 412 | elseif (otherSolutionType == "b1equal") then 413 | return createConditionBoolean(false); 414 | end 415 | elseif (ourSolutionType == "b1superior") then 416 | if (otherSolutionType == "b1superior") or (otherSolutionType == "b1max") then 417 | local k1l = cond.solve(); 418 | local k1b = otherCond.solve(); 419 | 420 | if (doPrintDebug) and (cfg_output_trivial_calc) then 421 | output_math_debug( "SUPCHECK: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 422 | end 423 | 424 | return createConditionBoolean( _math_leq(k1l, k1b) ); 425 | elseif (otherSolutionType == "b1equal") then 426 | return createConditionBoolean(false); 427 | end 428 | end 429 | 430 | math_assert( false, "DISAMB ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 431 | 432 | return false; 433 | end 434 | 435 | function cond.calcValidityUpper(otherCond, doPrintDebug) 436 | return sharedCalculateValidityUpper2DInequality(cond, otherCond, doPrintDebug); 437 | end 438 | 439 | return cond; 440 | end 441 | _G.createCondition2DLinearInequalityLT = createCondition2DLinearInequalityLT; 442 | 443 | -- 0 <= a1*k1 + b1*k2 + k3 444 | local function createCondition2DLinearInequalityLTEQ(k1, k2, k3) 445 | -- Pre-calculate the condition. 446 | local solutionType; 447 | local is_a1_bound = false; 448 | local is_b1_bound = false; 449 | local c1, c2; 450 | 451 | if not (_math_eq(k1, 0)) then 452 | if (k1 > 0) then 453 | solutionType = "a1min"; 454 | else 455 | solutionType = "a1max"; 456 | end 457 | 458 | is_a1_bound = true; 459 | c1 = (-k2)/k1; 460 | c2 = (-k3)/k1; 461 | elseif not (_math_eq(k2, 0)) then 462 | if (k2 > 0) then 463 | solutionType = "b1min"; 464 | else 465 | solutionType = "b1max"; 466 | end 467 | 468 | is_b1_bound = true; 469 | c1 = (-k3)/k2; 470 | else 471 | return createConditionBoolean( _math_leq(0, k3) ); 472 | end 473 | 474 | local cond = {}; 475 | 476 | function cond.getSolutionType() 477 | return solutionType; 478 | end 479 | 480 | function cond.solve() 481 | if (is_a1_bound) then 482 | -- a1 = c1*b1 + c2 483 | return c1, c2; 484 | elseif (is_b1_bound) then 485 | -- b1 = c1 486 | return c1; 487 | end 488 | end 489 | 490 | function cond.toStringEQ() 491 | if (solutionType == "a1max") or (solutionType == "a1min") then 492 | return mcs_multsum({c1, c2}, {"b1"}); 493 | elseif (solutionType == "b1max") or (solutionType == "b1min") then 494 | return tostring( c1 ); 495 | end 496 | 497 | return "unknown"; 498 | end 499 | 500 | function cond.toString() 501 | if (solutionType == "a1max") then 502 | return "a1 <= " .. cond.toStringEQ(); 503 | elseif (solutionType == "a1min") then 504 | return "a1 >= " .. cond.toStringEQ(); 505 | elseif (solutionType == "b1max") then 506 | return "b1 <= " .. cond.toStringEQ(); 507 | elseif (solutionType == "b1min") then 508 | return "b1 >= " .. cond.toStringEQ(); 509 | elseif (solutionType == "boolean") then 510 | return tostring( cond.solve() ); 511 | end 512 | 513 | return "unknown"; 514 | end 515 | 516 | -- Returns a condition that is required to be valid exactly-when that condition is valid in comparison 517 | -- to given other condition. 518 | function cond.disambiguate(otherCond, doPrintDebug) 519 | local ourSolutionType = cond.getSolutionType(); 520 | local otherSolutionType = otherCond.getSolutionType(); 521 | 522 | if (ourSolutionType == "a1min") then 523 | if (otherSolutionType == "a1min") then 524 | local k1b, k2b = cond.solve(); 525 | local k1l, k2l = otherCond.solve(); 526 | 527 | if (doPrintDebug) then 528 | output_math_debug( "SOLINF: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 529 | end 530 | 531 | return createCondition2DLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 532 | elseif (otherSolutionType == "a1inferior") then 533 | local k1rb, k2rb = cond.solve(); 534 | local k1rl, k2rl = otherCond.solve(); 535 | 536 | if (doPrintDebug) then 537 | output_math_debug( "SOLINF: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 538 | end 539 | 540 | return createCondition2DLinearInequalityLT( 0, k1rb - k1rl, k2rb - k2rl ); 541 | elseif (otherSolutionType == "a1equal") then 542 | return createConditionBoolean(false); 543 | end 544 | elseif (ourSolutionType == "a1max") then 545 | if (otherSolutionType == "a1max") then 546 | local k1l, k2l = cond.solve(); 547 | local k1b, k2b = otherCond.solve(); 548 | 549 | if (doPrintDebug) then 550 | output_math_debug( "SOLSUP: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 551 | end 552 | 553 | return createCondition2DLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 554 | elseif (otherSolutionType == "a1superior") then 555 | local k1rl, k2rl = cond.solve(); 556 | local k1rb, k2rb = otherCond.solve(); 557 | 558 | if (doPrintDebug) then 559 | output_math_debug( "SOLSUP: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 560 | end 561 | 562 | return createCondition2DLinearInequalityLT( 0, k1rb - k1rl, k2rb - k2rl ); 563 | elseif (otherSolutionType == "a1equal") then 564 | return createConditionBoolean(false); 565 | end 566 | elseif (ourSolutionType == "b1min") then 567 | if (otherSolutionType == "b1min") then 568 | local k1b = cond.solve(); 569 | local k1l = otherCond.solve(); 570 | 571 | if (doPrintDebug) and (cfg_output_trivial_calc) then 572 | output_math_debug( "INFCHECK: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 573 | end 574 | 575 | return createConditionBoolean( _math_geq(k1b, k1l) ); 576 | elseif (otherSolutionType == "b1inferior") then 577 | local k1rb = cond.solve(); 578 | local k1rl = otherCond.solve(); 579 | 580 | if (doPrintDebug) and (cfg_output_trivial_calc) then 581 | output_math_debug( "INFCHECK: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 582 | end 583 | 584 | return createConditionBoolean( k1rb > k1rl ); 585 | elseif (otherSolutionType == "b1equal") then 586 | return createConditionBoolean(false); 587 | end 588 | elseif (ourSolutionType == "b1max") then 589 | if (otherSolutionType == "b1max") then 590 | local k1l = cond.solve(); 591 | local k1b = otherCond.solve(); 592 | 593 | if (doPrintDebug) and (cfg_output_trivial_calc) then 594 | output_math_debug( "SUPCHECK: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 595 | end 596 | 597 | return createConditionBoolean( _math_leq(k1l, k1b) ); 598 | elseif (otherSolutionType == "b1superior") then 599 | local k1rl = cond.solve(); 600 | local k1rb = otherCond.solve(); 601 | 602 | if (doPrintDebug) and (cfg_output_trivial_calc) then 603 | output_math_debug( "SUPCHECK: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 604 | end 605 | 606 | return createConditionBoolean( k1rl < k1rb ); 607 | elseif (otherSolutionType == "b1equal") then 608 | return createConditionBoolean(false); 609 | end 610 | end 611 | 612 | math_assert( false, "DISAMB ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 613 | 614 | return false; 615 | end 616 | 617 | function cond.calcValidityUpper(otherCond, doPrintDebug) 618 | return sharedCalculateValidityUpper2DInequality(cond, otherCond, doPrintDebug); 619 | end 620 | 621 | return cond; 622 | end 623 | _G.createCondition2DLinearInequalityLTEQ = createCondition2DLinearInequalityLTEQ; -------------------------------------------------------------------------------- /math_server.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | -- TODO: draw some image and send it to the requesting client. 3 | local do_print_math_debug = false; 4 | 5 | local viewFrustum = createViewFrustum( 6 | createVector(0, 0, 3), 7 | createVector(5, 0, 0), 8 | createVector(0, 0, 5), 9 | createVector(0, 20, 0) 10 | ); 11 | 12 | local test_planes = { 13 | createPlane( 14 | createVector(-2, 0, -2), 15 | createVector(4, 0, 0), 16 | createVector(0, 20, 4) 17 | ), 18 | createPlane( 19 | createVector(-2, 25, -2), 20 | createVector(4, 0, 0), 21 | createVector(0, -14, 10) 22 | ), 23 | createPlane( 24 | createVector(-2, 0, -6), 25 | createVector(0, 20, 0), 26 | createVector(0, 0, 20) 27 | ), 28 | createPlane( 29 | createVector(2, 0, -6), 30 | createVector(0, 20, 0), 31 | createVector(0, 0, 20) 32 | ) 33 | }; 34 | 35 | -- Created by The_GTA. If you use this please mention my name somewhere. 36 | -- Script to create a render buffer out of a scene. 37 | local string = string; 38 | 39 | local function clamp_color(val) 40 | val = math.floor(val); 41 | 42 | if (val >= 255) then 43 | return 255; 44 | end 45 | 46 | if (val <= 0) then 47 | return 0; 48 | end 49 | 50 | return val; 51 | end 52 | 53 | local function to_color_item(a, r, g, b) 54 | return string.char(clamp_color(b)) .. string.char(clamp_color(g)) .. string.char(clamp_color(r)) .. string.char(clamp_color(a)); 55 | end 56 | 57 | local function color_idx(x, y, width) 58 | return ( ( y * width ) + x ) + 1; 59 | end 60 | 61 | -- Creates a backbuffer which is a string of bytes that can have certain dimensions. 62 | -- Can be merged using table.concat to be fed to texture engines. 63 | local function create_backbuffer( width, height, alphaClear, redClear, greenClear, blueClear ) 64 | local bbuf = {}; 65 | local items = {}; 66 | 67 | local clear_color = to_color_item(alphaClear, redClear, greenClear, blueClear); 68 | 69 | for y=0,height-1,1 do 70 | for x=0,width-1,1 do 71 | local color_idx = color_idx(x, y, width); 72 | 73 | items[color_idx] = clear_color; 74 | end 75 | end 76 | 77 | bbuf.width = width; 78 | bbuf.height = height; 79 | bbuf.items = items; 80 | 81 | return bbuf; 82 | end 83 | _G.create_backbuffer = create_backbuffer; 84 | 85 | -- Create a depth buffer, storing depth values of screen pixels. 86 | local function createDepthBuffer( width, height, clearValue ) 87 | local dbuf = {}; 88 | local items = {}; 89 | 90 | for y=0,height-1,1 do 91 | for x=0,width-1,1 do 92 | local color_idx = color_idx(x, y, width); 93 | 94 | items[color_idx] = clearValue; 95 | end 96 | end 97 | 98 | dbuf.width = width; 99 | dbuf.height = height; 100 | dbuf.items = items; 101 | 102 | return dbuf; 103 | end 104 | _G.createDepthBuffer = createDepthBuffer; 105 | 106 | -- Sets the color of a single pixel on the backbuffer, if possible. 107 | local function set_pixel_on_bbuf(bbuf, xpos, ypos, alpha, red, green, blue) 108 | local width = bbuf.width; 109 | local height = bbuf.height; 110 | 111 | local ypos_int = math.floor(ypos); 112 | 113 | if ( ypos_int < 0 ) or ( ypos_int >= height ) then 114 | return false; 115 | end 116 | 117 | local xpos_int = math.floor(xpos); 118 | 119 | if ( xpos_int < 0 ) or ( xpos_int >= width ) then 120 | return false; 121 | end 122 | 123 | local cidx = color_idx(xpos_int, ypos_int, width); 124 | 125 | bbuf.items[cidx] = to_color_item(alpha, red, green, blue); 126 | return true; 127 | end 128 | 129 | local function browse_depth(dbuf, xpos, ypos) 130 | local width = dbuf.width; 131 | local height = dbuf.height; 132 | 133 | local ypos_int = math.floor(ypos); 134 | 135 | if ( ypos_int < 0 ) or ( ypos_int >= height ) then 136 | return 1; 137 | end 138 | 139 | local xpos_int = math.floor(xpos); 140 | 141 | if ( xpos_int < 0 ) or ( xpos_int >= width ) then 142 | return 1; 143 | end 144 | 145 | local cidx = color_idx(xpos_int, ypos_int, width); 146 | 147 | return dbuf.items[cidx]; 148 | end 149 | 150 | local function update_depth(dbuf, xpos, ypos, newDepth) 151 | local width = dbuf.width; 152 | local height = dbuf.height; 153 | 154 | local ypos_int = math.floor(ypos); 155 | 156 | if ( ypos_int < 0 ) or ( ypos_int >= height ) then 157 | return false; 158 | end 159 | 160 | local xpos_int = math.floor(xpos); 161 | 162 | if ( xpos_int < 0 ) or ( xpos_int >= width ) then 163 | return false; 164 | end 165 | 166 | local cidx = color_idx(xpos_int, ypos_int, width); 167 | 168 | dbuf.items[cidx] = newDepth; 169 | return true; 170 | end 171 | 172 | local function to_real_coord(val) 173 | return ( val/2 + 0.5 ); 174 | end 175 | 176 | local function to_frustum_coord(val) 177 | return ( val - 0.5 ) * 2; 178 | end 179 | 180 | local function calc_depth(c1k1, c1k2, c1k3, c1k4, b1, a1) 181 | local divisor = ( c1k1 * a1 + c1k2 * b1 + c1k3 ); 182 | 183 | if (divisor == 0) then 184 | return 0; 185 | end 186 | 187 | return ( c1k4 / divisor ); 188 | end 189 | 190 | local function rasterize_intersection(inter, bbuf, cb) 191 | local function eval_a1_interval(a1k1, a1k2, pt) 192 | return ( a1k1 * pt + a1k2 ); 193 | end 194 | 195 | local function min_a1_interval(a1k1, a1k2, b1min, b1max) 196 | --assert(b1min <= b1max); 197 | 198 | if (_math_eq(a1k1, 0)) then 199 | return a1k2; 200 | elseif (a1k1 > 0) then 201 | return ( a1k1 * b1min + a1k2 ); 202 | else 203 | return ( a1k1 * b1max + a1k2 ); 204 | end 205 | end 206 | 207 | local function max_a1_interval(a1k1, a1k2, b1min, b1max) 208 | --assert(b1min <= b1max); 209 | 210 | if (_math_eq(a1k1, 0)) then 211 | return a1k2; 212 | elseif (a1k1 > 0) then 213 | return ( a1k1 * b1max + a1k2 ); 214 | else 215 | return ( a1k1 * b1min + a1k2 ); 216 | end 217 | end 218 | 219 | local taskUpdate = taskUpdate; 220 | 221 | for m,n in ipairs(inter) do 222 | local min_v = n.b1lower; 223 | local max_v = n.b1upper; 224 | local min_u = min_a1_interval(n.a1lower[1], n.a1lower[2], min_v, max_v); 225 | local max_u = max_a1_interval(n.a1upper[1], n.a1upper[2], min_v, max_v); 226 | 227 | -- Transform to real coordinates. 228 | min_v = to_real_coord( min_v ); 229 | max_v = to_real_coord( max_v ); 230 | min_u = to_real_coord( min_u ); 231 | max_u = to_real_coord( max_u ); 232 | 233 | local diff_v = ( max_v - min_v ); 234 | 235 | local diff_v_pixels = math.ceil(diff_v * bbuf.height); 236 | 237 | local start_y = math.floor(min_v * bbuf.height); 238 | 239 | for y=0,diff_v_pixels-1,1 do 240 | local abs_y = ( start_y + y ); 241 | local abs_v = math.max(math.min(abs_y / bbuf.height, max_v), min_v); 242 | 243 | -- Since we know the dimensions of the to-be-drawn surface we can process by scan-lines. 244 | local frustum_v = to_frustum_coord(abs_v); 245 | local block_min_u = eval_a1_interval(n.a1lower[1], n.a1lower[2], frustum_v); 246 | local block_max_u = eval_a1_interval(n.a1upper[1], n.a1upper[2], frustum_v); 247 | 248 | -- Transform to real coordinates. 249 | block_min_u = to_real_coord( block_min_u ); 250 | block_max_u = to_real_coord( block_max_u ); 251 | 252 | local block_diff_u = math.min( block_max_u - block_min_u, max_u - min_u ); 253 | 254 | local block_diff_u_pixels = math.ceil( block_diff_u * bbuf.width ); 255 | 256 | local block_start_x = math.floor( block_min_u * bbuf.width ); 257 | 258 | cb( n, block_start_x, abs_y, frustum_v, block_diff_u_pixels ); 259 | end 260 | end 261 | end 262 | 263 | local function draw_plane_on_bbuf(viewFrustum, bbuf, dbuf, plane, is_task, prim_type, doDebugOverride) 264 | if (is_task) then 265 | taskUpdate(false, "calculating intersection"); 266 | end 267 | 268 | local inter = false; 269 | 270 | if not (prim_type) or (prim_type == "plane") then 271 | inter = viewFrustum.intersectWithPlane(plane, do_print_math_debug or doDebugOverride); 272 | elseif (prim_type == "tri") then 273 | inter = viewFrustum.intersectWithTrianglePlane(plane, do_print_math_debug or doDebugOverride); 274 | end 275 | 276 | if not ( inter ) then 277 | return false; 278 | end 279 | 280 | -- Calculate the real amount of pixels. 281 | local max_pixels = 0; 282 | 283 | local function count_pixels(segment, row_off_x, row_y, frustum_v, row_width) 284 | max_pixels = max_pixels + row_width; 285 | end 286 | 287 | rasterize_intersection(inter, bbuf, count_pixels); 288 | 289 | -- Go through each valid u coordinate and draw all the associated v coordinates. 290 | local num_drawn_pixels = 0; 291 | local num_skipped_pixels = 0; 292 | 293 | local function draw_row(segment, row_off_x, row_y, frustum_v, row_width) 294 | for x=0,row_width-1,1 do 295 | local abs_x = ( row_off_x + x ); 296 | 297 | local abs_u = ( abs_x / bbuf.width ); 298 | 299 | -- Calculate the u coord in frustum space. 300 | local frustum_u = to_frustum_coord(abs_u); 301 | 302 | local depth = calc_depth( segment.c1lower[1], segment.c1lower[2], segment.c1lower[3], segment.c1lower[4], frustum_v, frustum_u ); 303 | 304 | local real_x = abs_x; 305 | local real_y = bbuf.height - row_y - 1; 306 | 307 | local invdepth = (1 - depth); 308 | 309 | local old_depth = browse_depth(dbuf, real_x, real_y); 310 | 311 | local has_drawn = false; 312 | 313 | if (depth < old_depth) then 314 | has_drawn = set_pixel_on_bbuf(bbuf, real_x, real_y, 255, 150 * invdepth, 150 * invdepth, 150 * invdepth); 315 | 316 | update_depth(dbuf, real_x, real_y, depth); 317 | end 318 | 319 | if (has_drawn) then 320 | num_drawn_pixels = num_drawn_pixels + 1; 321 | else 322 | num_skipped_pixels = num_skipped_pixels + 1; 323 | end 324 | end 325 | 326 | if (is_task) then 327 | --Update the running task. 328 | taskUpdate((num_drawn_pixels + num_skipped_pixels) / max_pixels, "rendering pixel " .. num_drawn_pixels .. " and skipped " .. num_skipped_pixels); 329 | end 330 | end 331 | 332 | if (is_task) then 333 | taskUpdate(0, "preparing loop"); 334 | end 335 | 336 | if (max_pixels >= 1) then 337 | rasterize_intersection(inter, bbuf, draw_row); 338 | end 339 | 340 | return true, num_drawn_pixels, num_skipped_pixels, max_pixels; 341 | end 342 | _G.draw_plane_on_bbuf = draw_plane_on_bbuf; 343 | 344 | -- It is actually undocumented, but plain pixels have two unsigned shorts that define the 345 | -- size of the image at the tail. 346 | 347 | local function num_to_ushort_bytes(num) 348 | local integer = math.floor(num); 349 | local lower = ( integer % 256 ); 350 | local upper = math.floor( integer / 256 ) % 256; 351 | 352 | return string.char(lower, upper); 353 | end 354 | _G.num_to_ushort_bytes = num_to_ushort_bytes; 355 | 356 | -- Draws a backbuffer and sends it in "plain" format to all clients. 357 | local function task_draw_scene(thread) 358 | local bbuf = create_backbuffer(640, 480, 255, 255, 0, 50); 359 | local dbuf = createDepthBuffer(640, 480, 1); 360 | 361 | local time_start = getTickCount(); 362 | 363 | for m,n in ipairs(test_planes) do 364 | local gotToDraw, numDrawn, numSkipped = draw_plane_on_bbuf(viewFrustum, bbuf, dbuf, n, true); 365 | 366 | if ( gotToDraw ) then 367 | outputDebugString( "drawn " .. numDrawn .. " pixels (skipped " .. numSkipped .. ")" ); 368 | end 369 | end 370 | 371 | local time_end = getTickCount(); 372 | local ms_diff = ( time_end - time_start ); 373 | 374 | outputDebugString( "render time: " .. ms_diff .. "ms" ); 375 | 376 | taskUpdate( 1, "creating backbuffer color composition string" ); 377 | 378 | local bbuf_width_ushort = num_to_ushort_bytes( bbuf.width ); 379 | local bbuf_height_ushort = num_to_ushort_bytes( bbuf.height ); 380 | 381 | local pixels_str = table.concat(bbuf.items); 382 | 383 | local bbuf_string = 384 | pixels_str .. 385 | ( bbuf_width_ushort .. 386 | bbuf_height_ushort ); 387 | 388 | taskUpdate( false, "sending backbuffer to clients (render time: " .. ms_diff .. "ms)" ); 389 | 390 | local players = getElementsByType("player"); 391 | 392 | for m,n in ipairs(players) do 393 | triggerClientEvent(n, "onServerTransmitImage", root, bbuf_string); 394 | end 395 | 396 | outputDebugString("sent backbuffer to clients"); 397 | end 398 | 399 | local modelToDraw = false; 400 | 401 | do 402 | local modelFile = fileOpen("gfriend.dff"); 403 | 404 | if (modelFile) then 405 | modelToDraw = rwReadClump(modelFile); 406 | fileClose(modelFile); 407 | end 408 | end 409 | 410 | local function task_draw_model(thread) 411 | local bbuf = create_backbuffer(640, 480, 255, 255, 0, 50); 412 | local dbuf = createDepthBuffer(640, 480, 1); 413 | 414 | local time_start = getTickCount(); 415 | 416 | local num_triangles_drawn = 0; 417 | 418 | if (modelToDraw) then 419 | -- Setup the camera. 420 | local geom = modelToDraw.geomlist[1]; 421 | local mt = geom.morphTargets[1]; 422 | local centerSphere = mt.sphere; 423 | 424 | local camPos = viewFrustum.getPos(); 425 | camPos.setX(centerSphere.x); 426 | camPos.setY(centerSphere.y - 3.8); 427 | camPos.setZ(centerSphere.z); 428 | 429 | local camFront = viewFrustum.getFront(); 430 | camFront.setX(0); 431 | camFront.setY(5 + centerSphere.r * 2); 432 | camFront.setZ(0); 433 | 434 | local camRight = viewFrustum.getRight(); 435 | camRight.setX(centerSphere.r * 2); 436 | camRight.setY(0); 437 | camRight.getZ(0); 438 | 439 | local camUp = viewFrustum.getUp(); 440 | camUp.setX(0); 441 | camUp.setY(0); 442 | camUp.setZ(centerSphere.r * 2); 443 | 444 | local triPlane = createPlane( 445 | createVector(0, 0, 0), 446 | createVector(0, 0, 0), 447 | createVector(0, 0, 0) 448 | ); 449 | 450 | local vertices = modelToDraw.geomlist[1].morphTargets[1].vertices; 451 | local triangles = modelToDraw.geomlist[1].triangles; 452 | 453 | local tpos = triPlane.getPos(); 454 | local tu = triPlane.getU(); 455 | local tv = triPlane.getV(); 456 | 457 | for m,n in ipairs(triangles) do 458 | taskUpdate( m / #triangles, "drawing triangle #" .. m ); 459 | 460 | local vert1 = vertices[n.vertex1 + 1]; 461 | local vert2 = vertices[n.vertex2 + 1]; 462 | local vert3 = vertices[n.vertex3 + 1]; 463 | 464 | tpos.setX(vert1.x); 465 | tpos.setY(vert1.y); 466 | tpos.setZ(vert1.z); 467 | 468 | tu.setX(vert2.x - vert1.x); 469 | tu.setY(vert2.y - vert1.y); 470 | tu.setZ(vert2.z - vert1.z); 471 | 472 | tv.setX(vert3.x - vert1.x); 473 | tv.setY(vert3.y - vert1.y); 474 | tv.setZ(vert3.z - vert1.z); 475 | 476 | local gotToDraw, numDrawn, numSkipped = draw_plane_on_bbuf(viewFrustum, bbuf, dbuf, triPlane, false, "tri"); 477 | 478 | if (gotToDraw) and (numDrawn >= 1) then 479 | num_triangles_drawn = num_triangles_drawn + 1; 480 | end 481 | end 482 | end 483 | 484 | local time_end = getTickCount(); 485 | local ms_diff = ( time_end - time_start ); 486 | 487 | outputDebugString( "render time: " .. ms_diff .. "ms, num drawn triangles: " .. num_triangles_drawn ); 488 | 489 | taskUpdate( 1, "creating backbuffer color composition string" ); 490 | 491 | local bbuf_width_ushort = num_to_ushort_bytes( bbuf.width ); 492 | local bbuf_height_ushort = num_to_ushort_bytes( bbuf.height ); 493 | 494 | local pixels_str = table.concat(bbuf.items); 495 | 496 | local bbuf_string = 497 | pixels_str .. 498 | ( bbuf_width_ushort .. 499 | bbuf_height_ushort ); 500 | 501 | taskUpdate( false, "sending backbuffer to clients (render time: " .. ms_diff .. "ms)" ); 502 | 503 | local players = getElementsByType("player"); 504 | 505 | for m,n in ipairs(players) do 506 | triggerClientEvent(n, "onServerTransmitImage", root, bbuf_string); 507 | end 508 | 509 | outputDebugString("sent backbuffer to clients"); 510 | end 511 | 512 | addCommandHandler( "send_bbuf", function(player) 513 | spawnTask(task_draw_scene); 514 | end 515 | ); 516 | 517 | addCommandHandler( "draw_model", function(player) 518 | spawnTask(task_draw_model); 519 | end 520 | ); 521 | 522 | setTimer( 523 | function() 524 | threads_pulse(); 525 | end, 50, 0 526 | ); -------------------------------------------------------------------------------- /math_server_testing.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | local output_debug_of_compare = false; 3 | 4 | local function fpe(a, b) 5 | -- After all, the FPU is not made to be accurate. 6 | return math.abs(a - b) < 0.001; 7 | end 8 | 9 | local function fpe_quot4(a1, a2, a3, a4, b1, b2, b3, b4) 10 | if ( _math_eq(a4, 0) ) then 11 | return _math_eq(b4, 0); 12 | elseif ( _math_eq(b4, 0) ) then 13 | return _math_eq(a4, 0); 14 | end 15 | 16 | local d1a = a1/a4; 17 | local d2a = a2/a4; 18 | local d3a = a3/a4; 19 | 20 | local d1b = b1/b4; 21 | local d2b = b2/b4; 22 | local d3b = b3/b4; 23 | 24 | return fpe(d1a, d1b) and fpe(d2a, d2b) and fpe(d3a, d3b); 25 | end 26 | 27 | local function find_inter_result( 28 | inter, 29 | b1lower, b1upper, 30 | a1lowerk1, a1lowerk2, a1upperk1, a1upperk2, 31 | c1lowerk1, c1lowerk2, c1lowerk3, c1lowerk4, c1upperk1, c1upperk2, c1upperk3, c1upperk4 32 | ) 33 | 34 | local found = false; 35 | 36 | for m,n in ipairs(inter) do 37 | if (output_debug_of_compare) then 38 | print( 39 | "(" .. b1lower .. ", " .. b1upper .. ") = (" .. n.b1lower .. ", " .. n.b1upper .. "), " .. 40 | "(" .. a1lowerk1 .. ", " .. a1lowerk2 .. ", " .. a1upperk1 .. ", " .. a1upperk2 .. ") = (" .. n.a1lower[1] .. ", " .. n.a1lower[2] .. ", " .. n.a1upper[1] .. ", " .. n.a1upper[2] .. "), " .. 41 | "(" .. c1lowerk1 .. ", " .. c1lowerk2 .. ", " .. c1lowerk3 .. ", " .. c1lowerk4 .. ", " .. c1upperk1 .. ", " .. c1upperk2 .. ", " .. c1upperk3 .. ", " .. c1upperk4 .. ") = (" .. 42 | n.c1lower[1] .. ", " .. n.c1lower[2] .. ", " .. n.c1lower[3] .. ", " .. n.c1lower[4] .. ", " .. n.c1upper[1] .. ", " .. n.c1upper[2] .. ", " .. n.c1upper[3] .. ", " .. n.c1upper[4] .. ")" 43 | ); 44 | end 45 | 46 | if (fpe(n.b1lower, b1lower)) and (fpe(n.b1upper, b1upper)) and 47 | (fpe(n.a1lower[1], a1lowerk1)) and (fpe(n.a1lower[2], a1lowerk2)) and 48 | (fpe(n.a1upper[1], a1upperk1)) and (fpe(n.a1upper[2], a1upperk2)) and 49 | (fpe_quot4( n.c1lower[1], n.c1lower[2], n.c1lower[3], n.c1lower[4], c1lowerk1, c1lowerk2, c1lowerk3, c1lowerk4 )) and 50 | (fpe_quot4( n.c1upper[1], n.c1upper[2], n.c1upper[3], n.c1upper[4], c1upperk1, c1upperk2, c1upperk3, c1upperk4 )) then 51 | 52 | found = true; 53 | break; 54 | end 55 | end 56 | 57 | if not ( found ) then 58 | error( "unit test fail", 2 ); 59 | end 60 | 61 | if (output_debug_of_compare) then 62 | outputConsole("found"); 63 | end 64 | end 65 | 66 | local function frustum_unit_test() 67 | -- All those unit tests are verified math on paper. 68 | if (true) then 69 | -- 3D frustum-plane non-linear intersection - example #6 70 | -- https://1drv.ms/u/s!Ao_bx9imD7B8hJ4U2BiAV2LY5lnkxA?e=cgvN1R 71 | local plane = createPlane( 72 | createVector( -2, 0, 2 ), 73 | createVector( 4, 0, 0 ), 74 | createVector( 0, 6, 0 ) 75 | ); 76 | 77 | local frustum = createViewFrustum( 78 | createVector( 0, 0, 0 ), 79 | createVector( 4, 0, 0 ), 80 | createVector( 0, 0, 4 ), 81 | createVector( 0, 8, 0 ) 82 | ); 83 | 84 | local inter = frustum.intersectWithPlane( plane, false ); 85 | 86 | assert( #inter >= 3 ); 87 | 88 | find_inter_result( 89 | inter, 90 | 2/3, 1, 91 | 0, 0, 0, 0, 92 | 0, 4, 0, 2, 0, 4, 0, 2 93 | ); 94 | find_inter_result( 95 | inter, 96 | 2/3, 1, 97 | 0, 0, 1, 0, 98 | 0, 4, 0, 2, 0, 4, 0, 2 99 | ); 100 | find_inter_result( 101 | inter, 102 | 2/3, 1, 103 | -1, 0, 0, 0, 104 | 0, 4, 0, 2, 0, 4, 0, 2 105 | ); 106 | end 107 | 108 | if (true) then 109 | -- 3D frustum plane non-linear intersection - example #7 110 | -- https://1drv.ms/u/s!Ao_bx9imD7B8hJ4V1XdLo8fGTa5MXw?e=FEezxh 111 | local plane = createPlane( 112 | createVector(-3, 2, -3), 113 | createVector(4, 0, 0), 114 | createVector(0, 1, 4) 115 | ); 116 | 117 | local frustum = createViewFrustum( 118 | createVector(0, 0, 0), 119 | createVector(4, 0, 0), 120 | createVector(0, 0, 5), 121 | createVector(0, 10, 0) 122 | ); 123 | 124 | local inter = frustum.intersectWithPlane( plane, false ); 125 | 126 | assert( #inter >= 4 ); 127 | 128 | find_inter_result( 129 | inter, 130 | -1, 2/3, 131 | 0, 0, 0, 0, 132 | 0, 5/4, -10, -11/4, 0, 5/4, -10, -11/4 133 | ); 134 | find_inter_result( 135 | inter, 136 | -1, -4/5, 137 | 0, 0, 0, 1, 138 | 0, 5/4, -10, -11/4, 0, 5/4, -10, -11/4 139 | ); 140 | find_inter_result( 141 | inter, 142 | -4/5, 2/3, 143 | 0, 0, -5/44, 10/11, 144 | 0, 5/4, -10, -11/4, 0, 5/4, -10, -11/4 145 | ); 146 | find_inter_result( 147 | inter, 148 | -1, 2/3, 149 | 0, -1, 0, 0, 150 | 0, 5/4, -10, -11/4, 0, 5/4, -10, -11/4 151 | ); 152 | end 153 | 154 | if (true) then 155 | -- 3D frustum-plane non-linear intersection - example #8 156 | -- https://1drv.ms/u/s!Ao_bx9imD7B8hJ4WfvqXobUac3GoGA?e=fytPRS 157 | local plane = createPlane( 158 | createVector(-2, 8, -2), 159 | createVector(1, 1, 0), 160 | createVector(0, 1, 1) 161 | ); 162 | 163 | local frustum = createViewFrustum( 164 | createVector(0, 0, 0), 165 | createVector(6, 0, 0), 166 | createVector(0, 0, 8), 167 | createVector(0, 10, 0) 168 | ); 169 | 170 | local inter = frustum.intersectWithPlane( plane ); 171 | 172 | find_inter_result( 173 | inter, 174 | -15/54, -15/108, 175 | 8/30, -1/3, 4/33, -5/33, 176 | 6, 8, -10, -12, 6, 8, -10, -12 177 | ); 178 | find_inter_result( 179 | inter, 180 | -15/48, -15/54, 181 | 8/30, -1/3, 20/3, 5/3, 182 | 6, 8, -10, -12, 6, 8, -10, -12 183 | ); 184 | find_inter_result( 185 | inter, 186 | -1/8, -1/8, 187 | 44/3, 5/3, -4/3, -1/3, 188 | 6, 8, -10, -12, 6, 8, -10, -12 189 | ); 190 | find_inter_result( 191 | inter, 192 | -15/108, -1/8, 193 | 44/3, 5/3, 4/33, -5/33, 194 | 6, 8, -10, -12, 6, 8, -10, -12 195 | ); 196 | 197 | -- Performance test. 198 | if (false) then 199 | for n=1,100 do 200 | frustum.intersectWithPlane( plane, false ); 201 | end 202 | end 203 | end 204 | 205 | if (true) then 206 | -- 3D frustum-triangle non-linear intersection - example #1 207 | -- https://1drv.ms/u/s!Ao_bx9imD7B8hJ4TCbQ49eP2IDD7Sw?e=WYWd3t 208 | local plane = createPlane( 209 | createVector(-2, 6, -1), 210 | createVector(4, 0, 0), 211 | createVector(0, 0, 2) 212 | ); 213 | 214 | local frustum = createViewFrustum( 215 | createVector(0, 0, 0), 216 | createVector(6, 0, 0), 217 | createVector(0, 0, 5), 218 | createVector(0, 15, 0) 219 | ); 220 | 221 | local inter = frustum.intersectWithTrianglePlane(plane); 222 | 223 | find_inter_result( 224 | inter, 225 | -1/2, 0, 226 | 0, 0, -5/3, 0, 227 | 0, 0, 15, 6, 0, 0, 15, 6 228 | ); 229 | find_inter_result( 230 | inter, 231 | -1/2, 0, 232 | 0, -5/6, 0, 0, 233 | 0, 0, 15, 6, 0, 0, 15, 6 234 | ); 235 | find_inter_result( 236 | inter, 237 | 0, 1/2, 238 | 0, -5/6, -5/3, 0, 239 | 0, 0, 15, 6, 0, 0, 15, 6 240 | ); 241 | end 242 | 243 | if (true) then 244 | -- 3D frustum-plane non-linear intesection - example #9 245 | -- https://1drv.ms/u/s!Ao_bx9imD7B8hJ4SjRRGgkCfvrTfPg?e=Uw36JF 246 | local plane = createPlane( 247 | createVector(3, 4, 3), 248 | createVector(1, 0, 0), 249 | createVector(0, 0, 1) 250 | ); 251 | 252 | local frustum = createViewFrustum( 253 | createVector(0, 0, 0), 254 | createVector(4, 0, 0), 255 | createVector(0, 0, 3), 256 | createVector(0, 8, 0) 257 | ); 258 | 259 | local inter = frustum.intersectWithPlane(plane); 260 | 261 | assert( inter == false ); 262 | end 263 | end 264 | 265 | frustum_unit_test(); 266 | 267 | local function output_unit_test(str) 268 | outputDebugString(str); 269 | end 270 | 271 | local function tools_unit_test() 272 | local function is_relevant_a1_cond(solutionType) 273 | return ( solutionType == "a1equal" ) or 274 | ( solutionType == "a1min" ) or 275 | ( solutionType == "a1inferior" ) or 276 | ( solutionType == "a1max" ) or 277 | ( solutionType == "a1superior" ); 278 | end 279 | 280 | do 281 | local chain = createConditionAND(); 282 | 283 | chain.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 1)); 284 | chain.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 1)); 285 | 286 | local sub_or1 = createConditionOR(); 287 | 288 | sub_or1.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 2)); 289 | sub_or1.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 2)); 290 | 291 | chain.addVar(sub_or1); 292 | 293 | local sub_or2 = createConditionOR(); 294 | 295 | sub_or2.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 3)); 296 | sub_or2.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 3)); 297 | 298 | chain.addVar(sub_or2); 299 | 300 | chain = smart_group_condition(chain, is_relevant_a1_cond, is_relevant_a1_cond); 301 | 302 | assert( chain.toString() == "( ( a1 >= (-3) AND a1 >= (-2) AND a1 >= (-1) AND a1 <= 1 ) OR ( a1 >= (-3) AND a1 <= 2 AND a1 >= (-1) AND a1 <= 1 ) OR ( a1 <= 3 AND a1 >= (-2) AND a1 >= (-1) AND a1 <= 1 ) OR ( a1 <= 3 AND a1 <= 2 AND a1 >= (-1) AND a1 <= 1 ) )" ); 303 | end 304 | 305 | do 306 | local chain = createConditionAND(); 307 | 308 | chain.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 1)); 309 | chain.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 1)); 310 | 311 | local sub_or = createConditionOR(); 312 | 313 | do 314 | local first_cond = createConditionAND(); 315 | 316 | first_cond.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 2)); 317 | first_cond.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 2)); 318 | 319 | local subsub_or = createConditionOR(); 320 | 321 | subsub_or.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 3)); 322 | subsub_or.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 3)); 323 | 324 | first_cond.addVar(subsub_or); 325 | sub_or.addVar(first_cond); 326 | end 327 | 328 | do 329 | local second_cond = createConditionAND(); 330 | 331 | second_cond.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 4)); 332 | second_cond.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 5)); 333 | 334 | sub_or.addVar(second_cond); 335 | end 336 | 337 | chain.addVar(sub_or); 338 | 339 | chain = smart_group_condition(chain, is_relevant_a1_cond, is_relevant_a1_cond); 340 | 341 | -- assert... 342 | end 343 | 344 | do 345 | local chain = createConditionAND(); 346 | 347 | chain.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 1)); 348 | chain.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 1)); 349 | chain.addVar(createCondition2DLinearInequalityLTEQ(0, 1, 1)); 350 | chain.addVar(createCondition2DLinearInequalityLTEQ(0, -1, 1)); 351 | 352 | do 353 | local orCond = createConditionOR(); 354 | 355 | do 356 | local andCond = createConditionAND(); 357 | 358 | andCond.addVar(createConditionC13DLowerBoundNonNeg(0, 0, 1, 0, "pos")); 359 | andCond.addVar(createConditionC13DUpperBoundNonNeg(0, 0, 1, 1, "pos")); 360 | andCond.addVar(createCondition2DLinearEquality(1, 0, 0)); 361 | andCond.addVar(createCondition2DLinearEquality(0, -1, 2/3)); 362 | 363 | orCond.addVar(andCond); 364 | end 365 | 366 | do 367 | local andCond = createConditionAND(); 368 | 369 | andCond.addVar(createCondition2DLinearEquality(1, 0, 0)); 370 | andCond.addVar(createConditionC13DEqualityNonNeg(0, 0, 1, 0)); 371 | 372 | orCond.addVar(andCond); 373 | end 374 | 375 | do 376 | local sub_andCond = createConditionAND(); 377 | 378 | do 379 | local sub_orCond = createConditionOR(); 380 | 381 | do 382 | local subsub_andCond = createConditionAND(); 383 | 384 | subsub_andCond.addVar(createConditionC13DLowerBoundNonNeg(0, 0, 1, 0, "pos")); 385 | subsub_andCond.addVar(createConditionC13DUpperBoundNonNeg(3/2, 0, 0, 1/2, "pos")); 386 | subsub_andCond.addVar(createCondition2DLinearInequalityLTEQ(1, 0, -1/3)); 387 | 388 | sub_orCond.addVar(subsub_andCond); 389 | end 390 | 391 | do 392 | local subsub_andCond = createConditionAND(); 393 | 394 | subsub_andCond.addVar(createConditionC13DLowerBoundNonNeg(0, 0, 1, 0, "pos")); 395 | subsub_andCond.addVar(createConditionC13DUpperBoundNonNeg(0, 0, 1, 1, "pos")); 396 | subsub_andCond.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, 1/3)); 397 | 398 | sub_orCond.addVar(subsub_andCond); 399 | end 400 | 401 | sub_andCond.addVar(sub_orCond); 402 | end 403 | 404 | sub_andCond.addVar(createCondition2DLinearInequalityLT(1, 0, 0)); 405 | sub_andCond.addVar(createCondition2DLinearEquality(0, -1, 2/3)); 406 | 407 | orCond.addVar(sub_andCond); 408 | end 409 | 410 | do 411 | local sub_andCond = createConditionAND(); 412 | 413 | sub_andCond.addVar(createCondition2DLinearInequalityLT(1, 0, 0)); 414 | sub_andCond.addVar(createConditionC13DEqualityNonNeg(0, 0, 1, 0)); 415 | 416 | orCond.addVar(sub_andCond); 417 | end 418 | 419 | do 420 | local sub_andCond = createConditionAND(); 421 | 422 | do 423 | local sub_orCond = createConditionOR(); 424 | 425 | do 426 | local subsub_andCond = createConditionAND(); 427 | 428 | subsub_andCond.addVar(createConditionC13DLowerBoundNonNeg(0, 0, 1, 0, "pos")); 429 | subsub_andCond.addVar(createConditionC13DUpperBoundNonNeg(3/2, 0, 0, -1/2, "neg")); 430 | subsub_andCond.addVar(createCondition2DLinearInequalityLTEQ(-1, 0, -1/3)); 431 | 432 | sub_orCond.addVar(subsub_andCond); 433 | end 434 | 435 | do 436 | local subsub_andCond = createConditionAND(); 437 | 438 | subsub_andCond.addVar(createConditionC13DLowerBoundNonNeg(0, 0, 1, 0, "pos")); 439 | subsub_andCond.addVar(createConditionC13DUpperBoundNonNeg(0, 0, 1, 1, "pos")); 440 | subsub_andCond.addVar(createCondition2DLinearInequalityLTEQ(1, 0, 1/3)); 441 | 442 | sub_orCond.addVar(subsub_andCond); 443 | end 444 | 445 | sub_andCond.addVar(sub_orCond); 446 | end 447 | 448 | sub_andCond.addVar(createCondition2DLinearInequalityLT(-1, 0, 0)); 449 | sub_andCond.addVar(createCondition2DLinearEquality(0, 1, -2/3)); 450 | 451 | orCond.addVar(sub_andCond); 452 | end 453 | 454 | do 455 | local sub_andCond = createConditionAND(); 456 | 457 | sub_andCond.addVar(createCondition2DLinearInequalityLT(-1, 0, 0)); 458 | sub_andCond.addVar(createConditionC13DEqualityNonNeg(0, 0, 1, 0)); 459 | 460 | orCond.addVar(sub_andCond); 461 | end 462 | 463 | chain.addVar(orCond); 464 | end 465 | 466 | chain = smart_group_condition(chain, is_relevant_a1_cond, is_relevant_a1_cond); 467 | 468 | -- assert... 469 | end 470 | end 471 | 472 | tools_unit_test(); -------------------------------------------------------------------------------- /math_shared_absmirr.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | -- 0 = |a1|*k1 + |b1|*k2 + k3 3 | function createConditionAbsoluteLinearEquality(k1, k2, k3) 4 | local cond = {}; 5 | 6 | function cond.getSolutionType() 7 | if not (k1 == 0) then 8 | return "a1absequal"; 9 | elseif not (k2 == 0) then 10 | return "b1absequal"; 11 | end 12 | 13 | return "boolean"; 14 | end 15 | 16 | function cond.solve() 17 | if not (k1 == 0) then 18 | local c1 = (-k2)/k1; 19 | local c2 = (-k3)/k1; 20 | 21 | -- |a1| = c1*|b1| + c2 22 | return c1, c2; 23 | elseif not (k2 == 0) then 24 | local c1 = (-k3)/k2; 25 | 26 | -- |b1| = c1 27 | return c1; 28 | else 29 | -- boolean 30 | return ( k3 == 0 ); 31 | end 32 | end 33 | 34 | function cond.toStringEQ() 35 | local solutionType = cond.getSolutionType(); 36 | local c1, c2 = cond.solve(); 37 | 38 | if (solutionType == "a1absequal") then 39 | return c1 .. "*|b1| + " .. c2; 40 | elseif (solutionType == "b1absequal") then 41 | return tostring( c1 ); 42 | elseif (solutionType == "boolean") then 43 | return tostring( c1 ); 44 | end 45 | 46 | return "unknown"; 47 | end 48 | 49 | function cond.toString() 50 | local solutionType = cond.getSolutionType(); 51 | 52 | if (solutionType == "a1absequal") then 53 | return "|a1| = " .. cond.toStringEQ(); 54 | elseif (solutionType == "b1absequal") then 55 | return "|b1| = " .. cond.toStringEQ(); 56 | elseif (solutionType == "boolean") then 57 | return tostring( cond.solve() ); 58 | end 59 | 60 | return "unknown"; 61 | end 62 | 63 | function cond.disambiguate(otherCond) 64 | local ourSolutionType = cond.getSolutionType(); 65 | local otherSolutionType = otherCond.getSolutionType(); 66 | 67 | if (ourSolutionType == "a1absequal") then 68 | if (otherSolutionType == "a1min") then 69 | local k1b, k2b = cond.solve(); 70 | local k1l, k2l = otherCond.solve(); 71 | 72 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 73 | 74 | return createConditionAbsoluteLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 75 | elseif (otherSolutionType == "a1absmax") then 76 | local k1l, k2l = cond.solve(); 77 | local k1b, k2b = otherCond.solve(); 78 | 79 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 80 | 81 | return createConditionAbsoluteLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 82 | elseif (otherSolutionType == "a1absinferior") then 83 | local k1rb, k2rb = cond.solve(); 84 | local k1rl, k2rl = otherCond.solve(); 85 | 86 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 87 | 88 | return createConditionAbsoluteLinearInequality( 0, k1rb - k1rl, k2rb - k2rl ); 89 | elseif (otherSolutionType == "a1abssuperior") then 90 | local k1rl, k2rl = cond.solve(); 91 | local k1rb, k2rb = otherCond.solve(); 92 | 93 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 94 | 95 | return createConditionAbsoluteLinearInequality( 0, k1rb - k1rl, k2rb - k2rl ); 96 | elseif (otherSolutionType == "a1absequal") then 97 | -- Meow. We assume that if both are equal then both are valid. Could be a very evil assumption! 98 | return createConditionBoolean(true); 99 | end 100 | elseif (ourSolutionType == "b1absequal") then 101 | if (otherSolutionType == "b1absmin") then 102 | local k1b = cond.solve(); 103 | local k1l = otherCond.solve(); 104 | 105 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 106 | 107 | return createConditionBoolean(k1b >= k1l); 108 | elseif (otherSolutionType == "b1absmax") then 109 | local k1l = cond.solve(); 110 | local k1b = otherCond.solve(); 111 | 112 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 113 | 114 | return createConditionBoolean(k1l <= k1b); 115 | elseif (otherSolutionType == "b1absinferior") then 116 | local k1rb = cond.solve(); 117 | local k1rl = otherCond.solve(); 118 | 119 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 120 | 121 | return createConditionBoolean(k1rb > k1rl); 122 | elseif (otherSolutionType == "b1abssuperior") then 123 | local k1rl = cond.solve(); 124 | local k1rb = otherCond.solve(); 125 | 126 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 127 | 128 | return createConditionBoolean(k1rl < k1rb); 129 | elseif (otherSolutionType == "b1absequal") then 130 | local k1a = cond.solve(); 131 | local k1b = otherCond.solve(); 132 | 133 | output_math_debug( "DISAMBCHECK: " .. cond.toStringEQ() .. " = " .. otherCond.toStringEQ() ); 134 | 135 | return createConditionBoolean(k1a == k1b); 136 | end 137 | end 138 | 139 | output_math_debug( "DISAMB ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 140 | 141 | return false; 142 | end 143 | 144 | return cond; 145 | end 146 | 147 | -- 0 < |a1|*k1 + |b1|*k2 + k3 148 | function createConditionAbsoluteLinearInequality(k1, k2, k3) 149 | local cond = {}; 150 | local is_always_true = false; 151 | 152 | if (k1 <= 0) and (k2 <= 0) and (k3 <= 0) then 153 | k1 = 0; 154 | k2 = 0; 155 | k3 = 0; 156 | end 157 | 158 | if (k1 > 0) and (k2 > 0) and (k3 > 0) then 159 | k1 = 0; 160 | k2 = 0; 161 | k3 = 0; 162 | is_always_true = true; 163 | end 164 | 165 | function cond.getSolutionType() 166 | if (k1 > 0) then 167 | return "a1absinferior"; 168 | elseif (k1 < 0) then 169 | return "a1abssuperior"; 170 | elseif (k2 > 0) then 171 | return "b1absinferior"; 172 | elseif (k2 < 0) then 173 | return "b1abssuperior"; 174 | else 175 | return "boolean"; 176 | end 177 | end 178 | 179 | function cond.solve() 180 | if not (k1 == 0) then 181 | local c1 = (-k2)/k1; 182 | local c2 = (-k3)/k1; 183 | 184 | -- |a1| c1*|b1| + c2 185 | return c1, c2; 186 | elseif not (k2 == 0) then 187 | local c1 = (-k3)/k2; 188 | 189 | -- |b1| c1 190 | return c1; 191 | elseif (is_always_true) then 192 | return true; 193 | else 194 | return ( 0 < k3 ); 195 | end 196 | end 197 | 198 | function cond.toStringEQ() 199 | local solutionType = cond.getSolutionType(); 200 | local c1, c2 = cond.solve(); 201 | 202 | if (solutionType == "a1abssuperior") or (solutionType == "a1absinferior") then 203 | return c1 .. "*|b1| + " .. c2; 204 | elseif (solutionType == "b1abssuperior") or (solutionType == "b1absinferior") then 205 | return tostring( c1 ); 206 | elseif (solutionType == "boolean") then 207 | return tostring( c1 ); 208 | end 209 | 210 | return "unknown"; 211 | end 212 | 213 | function cond.toString() 214 | local solutionType = cond.getSolutionType(); 215 | 216 | if (solutionType == "a1abssuperior") then 217 | return "|a1| < " .. cond.toStringEQ(); 218 | elseif (solutionType == "a1absinferior") then 219 | return "|a1| > " .. cond.toStringEQ(); 220 | elseif (solutionType == "b1abssuperior") then 221 | return "|b1| < " .. cond.toStringEQ(); 222 | elseif (solutionType == "b1absinferior") then 223 | return "|b1| > " .. cond.toStringEQ(); 224 | elseif (solutionType == "boolean") then 225 | return tostring( cond.solve() ); 226 | end 227 | 228 | return "unknown"; 229 | end 230 | 231 | function cond.disambiguate(otherCond) 232 | local ourSolutionType = cond.getSolutionType(); 233 | local otherSolutionType = otherCond.getSolutionType(); 234 | 235 | if (ourSolutionType == "a1absinferior") then 236 | if (otherSolutionType == "a1absinferior") or (otherSolutionType == "a1absmin") then 237 | local k1b, k2b = cond.solve(); 238 | local k1l, k2l = otherCond.solve(); 239 | 240 | output_math_debug( "SOLINF: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 241 | 242 | return createConditionAbsoluteLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 243 | elseif (otherSolutionType == "a1absequal") then 244 | return createConditionBoolean(false); 245 | end 246 | elseif (ourSolutionType == "a1abssuperior") then 247 | if (otherSolutionType == "a1abssuperior") or (otherSolutionType == "a1absmax") then 248 | local k1l, k2l = cond.solve(); 249 | local k1b, k2b = otherCond.solve(); 250 | 251 | output_math_debug( "SOLSUP: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 252 | 253 | return createConditionAbsoluteLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 254 | elseif (otherSolutionType == "a1equal") then 255 | return createConditionBoolean(false); 256 | end 257 | elseif (ourSolutionType == "b1absinferior") then 258 | if (otherSolutionType == "b1absinferior") or (otherSolutionType == "b1absmin") then 259 | local k1b = cond.solve(); 260 | local k1l = otherCond.solve(); 261 | 262 | output_math_debug( "INFCHECK: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 263 | 264 | return createConditionBoolean( k1b >= k1l ); 265 | elseif (otherSolutionType == "b1absequal") then 266 | return createConditionBoolean(false); 267 | end 268 | elseif (ourSolutionType == "b1abssuperior") then 269 | if (otherSolutionType == "b1abssuperior") or (otherSolutionType == "b1absmax") then 270 | local k1l = cond.solve(); 271 | local k1b = otherCond.solve(); 272 | 273 | output_math_debug( "SUPCHECK: " .. cond.toStringEQ .. " <= " .. otherCond.toStringEQ() ); 274 | 275 | return createConditionBoolean( k1l <= k1b ); 276 | elseif (otherSolutionType == "b1absequal") then 277 | return createConditionBoolean(false); 278 | end 279 | end 280 | 281 | output_math_debug( "DISAMB ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 282 | 283 | return false; 284 | end 285 | 286 | return cond; 287 | end 288 | 289 | -- 0 <= |a1|*k1 + |b1|*k2 + k3 290 | function createConditionAbsoluteLinearInequalityLTEQ(k1, k2, k3) 291 | local cond = {}; 292 | local is_always_true = false; 293 | 294 | if (k1 < 0) and (k2 < 0) and (k3 < 0) then 295 | k1 = 0; 296 | k2 = 0; 297 | k3 = 0; 298 | end 299 | 300 | if (k1 >= 0) and (k2 >= 0) and (k3 >= 0) then 301 | k1 = 0; 302 | k2 = 0; 303 | k3 = 0; 304 | is_always_true = true; 305 | end 306 | 307 | function cond.getSolutionType() 308 | if (k1 > 0) then 309 | return "a1absmin"; 310 | elseif (k1 < 0) then 311 | return "a1absmax"; 312 | elseif (k2 > 0) then 313 | return "b1absmin"; 314 | elseif (k2 < 0) then 315 | return "b1absmax"; 316 | else 317 | return "boolean"; 318 | end 319 | end 320 | 321 | function cond.solve() 322 | if not (k1 == 0) then 323 | local c1 = (-k2)/k1; 324 | local c2 = (-k3)/k1; 325 | 326 | -- |a1| = c1*|b1| + c2 327 | return c1, c2; 328 | elseif not (k2 == 0) then 329 | local c1 = (-k3)/k2; 330 | 331 | -- |b1| = c1 332 | return c1; 333 | elseif (is_always_true) then 334 | return true; 335 | else 336 | return ( 0 <= k3 ); 337 | end 338 | end 339 | 340 | function cond.toStringEQ() 341 | local solutionType = cond.getSolutionType(); 342 | local c1, c2 = cond.solve(); 343 | 344 | if (solutionType == "a1absmax") or (solutionType == "a1absmin") then 345 | return c1 .. "*|b1| + " .. c2; 346 | elseif (solutionType == "b1absmax") or (solutionType == "b1absmin") then 347 | return tostring( c1 ); 348 | elseif (solutionType == "boolean") then 349 | return tostring( c1 ); 350 | end 351 | 352 | return "unknown"; 353 | end 354 | 355 | function cond.toString() 356 | local solutionType = cond.getSolutionType(); 357 | 358 | if (solutionType == "a1absmax") then 359 | return "|a1| <= " .. cond.toStringEQ(); 360 | elseif (solutionType == "a1absmin") then 361 | return "|a1| >= " .. cond.toStringEQ(); 362 | elseif (solutionType == "b1absmax") then 363 | return "|b1| <= " .. cond.toStringEQ(); 364 | elseif (solutionType == "b1absmin") then 365 | return "|b1| >= " .. cond.toStringEQ(); 366 | elseif (solutionType == "boolean") then 367 | return tostring( cond.solve() ); 368 | end 369 | 370 | return "unknown"; 371 | end 372 | 373 | -- Returns a condition that is required to be valid exactly-when that condition is valid in comparison 374 | -- to given other condition. 375 | function cond.disambiguate(otherCond) 376 | local ourSolutionType = cond.getSolutionType(); 377 | local otherSolutionType = otherCond.getSolutionType(); 378 | 379 | if (ourSolutionType == "a1absmin") then 380 | if (otherSolutionType == "a1absmin") then 381 | local k1b, k2b = cond.solve(); 382 | local k1l, k2l = otherCond.solve(); 383 | 384 | output_math_debug( "SOLINF: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 385 | 386 | return createConditionAbsoluteLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 387 | elseif (otherSolutionType == "a1absinferior") then 388 | local k1rb, k2rb = cond.solve(); 389 | local k1rl, k2rl = otherCond.solve(); 390 | 391 | output_math_debug( "SOLINF: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 392 | 393 | return createConditionAbsoluteLinearInequality( 0, k1rb - k1rl, k2rb - k2rl ); 394 | elseif (otherSolutionType == "a1absequal") then 395 | return createConditionBoolean(false); 396 | end 397 | elseif (ourSolutionType == "a1absmax") then 398 | if (otherSolutionType == "a1absmax") then 399 | local k1l, k2l = cond.solve(); 400 | local k1b, k2b = otherCond.solve(); 401 | 402 | output_math_debug( "SOLSUP: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 403 | 404 | return createConditionAbsoluteLinearInequalityLTEQ( 0, k1b - k1l, k2b - k2l ); 405 | elseif (otherSolutionType == "a1abssuperior") then 406 | local k1rl, k2rl = cond.solve(); 407 | local k1rb, k2rb = otherCond.solve(); 408 | 409 | output_math_debug( "SOLSUP: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 410 | 411 | return createConditionAbsoluteLinearInequality( 0, k1rb - k1rl, k2rb - k2rl ); 412 | elseif (otherSolutionType == "a1absequal") then 413 | return createConditionBoolean(false); 414 | end 415 | elseif (ourSolutionType == "b1absmin") then 416 | if (otherSolutionType == "b1absmin") then 417 | local k1b = cond.solve(); 418 | local k1l = otherCond.solve(); 419 | 420 | output_math_debug( "INFCHECK: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 421 | 422 | return createConditionBoolean( k1b >= k1l ); 423 | elseif (otherSolutionType == "b1absinferior") then 424 | local k1rb = cond.solve(); 425 | local k1rl = otherCond.solve(); 426 | 427 | output_math_debug( "INFCHECK: " .. cond.toStringEQ() .. " > " .. otherCond.toStringEQ() ); 428 | 429 | return createConditionBoolean( k1rb > k1rl ); 430 | elseif (otherSolutionType == "b1absequal") then 431 | return createConditionBoolean(false); 432 | end 433 | elseif (ourSolutionType == "b1absmax") then 434 | if (otherSolutionType == "b1absmax") then 435 | local k1l = cond.solve(); 436 | local k1b = otherCond.solve(); 437 | 438 | output_math_debug( "SUPCHECK: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 439 | 440 | return createConditionBoolean( k1l <= k1b ); 441 | elseif (otherSolutionType == "b1abssuperior") then 442 | local k1rl = cond.solve(); 443 | local k1rb = otherCond.solve(); 444 | 445 | output_math_debug( "SUPCHECK: " .. cond.toStringEQ() .. " < " .. otherCond.toStringEQ() ); 446 | 447 | return createConditionBoolean( k1rl < k1rb ); 448 | elseif (otherSolutionType == "b1absequal") then 449 | return createConditionBoolean(false); 450 | end 451 | end 452 | 453 | output_math_debug( "DISAMB ERROR: " .. ourSolutionType .. " and " .. otherSolutionType ); 454 | 455 | return false; 456 | end 457 | 458 | return cond; 459 | end 460 | 461 | local function helperCreateConditionAbsoluteLinearInequalityCompareLTEQ( k1l, k2l, k3l, k4l, k1b, k2b, k3b, k4b ) 462 | local c1 = (k4b*k1l - k4l*k1b); 463 | local c2 = (k4b*k2l - k4l*k2b); 464 | local c3 = (k4b*k3l - k4l*k3b); 465 | 466 | return createConditionAbsoluteLinearInequalityLTEQ( c1, c2, c3 ); 467 | end 468 | 469 | -- c1 >= k4 / ( k1*|a1| + k2*|b1| + k3 ) 470 | function createConditionC1AbsoluteLowerBound(k1, k2, k3, k4) 471 | local cond = {}; 472 | 473 | function cond.getSolutionType() 474 | return "c1min"; 475 | end 476 | 477 | function cond.solve() 478 | return k1, k2, k3, k4; 479 | end 480 | 481 | function cond.toStringEQ() 482 | return "(" .. k4 .. ") / ( " .. k1 .. "*|a1| + " .. k2 .. "*|b1| + " .. k3 .. " )"; 483 | end 484 | 485 | function cond.toString() 486 | return "c1 >= " .. cond.toStringEQ(); 487 | end 488 | 489 | function cond.disambiguate(otherCond) 490 | local otherSolutionType = otherCond.getSolutionType(); 491 | 492 | if (otherSolutionType == "c1min") then 493 | local mink1, mink2, mink3, mink4 = otherCond.solve(); 494 | 495 | output_math_debug( 496 | "SOLINF: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() 497 | ); 498 | 499 | local infopt = helperCreateConditionAbsoluteLinearInequalityCompareLTEQ( mink1, mink2, mink3, mink4, k1, k2, k3, k4 ); 500 | 501 | return infopt; 502 | elseif (otherSolutionType == "c1equality") then 503 | return createConditionBoolean(false); 504 | end 505 | 506 | return false; 507 | end 508 | 509 | return cond; 510 | end 511 | 512 | function createConditionC1AbsoluteUpperBound(k1, k2, k3, k4) 513 | local cond = {}; 514 | 515 | function cond.getSolutionType() 516 | return "c1max"; 517 | end 518 | 519 | function cond.solve() 520 | return k1, k2, k3, k4; 521 | end 522 | 523 | function cond.toStringEQ() 524 | return "(" .. k4 .. ") / ( " .. k1 .. "*|a1| + " .. k2 .. "*|b1| + " .. k3 .. " )"; 525 | end 526 | 527 | function cond.toString() 528 | return "c1 <= " .. cond.toStringEQ(); 529 | end 530 | 531 | function cond.disambiguate(otherCond) 532 | local otherSolutionType = otherCond.getSolutionType(); 533 | 534 | if (otherSolutionType == "c1max") then 535 | local maxk1, maxk2, maxk3, maxk4 = otherCond.solve(); 536 | 537 | output_math_debug( 538 | "SOLSUP: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() 539 | ); 540 | 541 | local supopt = helperCreateConditionAbsoluteLinearInequalityCompareLTEQ( k1, k2, k3, k4, maxk1, maxk2, maxk3, maxk4 ); 542 | 543 | return supopt; 544 | elseif (otherSolutionType == "c1equality") then 545 | return createConditionBoolean(false); 546 | end 547 | 548 | return false; 549 | end 550 | 551 | return cond; 552 | end 553 | 554 | -- c1 = k4 / ( k1*|a1| + k2*|b1| + k3 ) 555 | function createConditionC1AbsoluteEquality(k1, k2, k3, k4) 556 | local cond = {}; 557 | 558 | function cond.getSolutionType() 559 | return "c1equality"; 560 | end 561 | 562 | function cond.solve() 563 | return k1, k2, k3, k4; 564 | end 565 | 566 | function cond.toStringEQ() 567 | return "(" .. k4 .. ") / ( " .. k1 .. "*|a1| + " .. k2 .. "*|b1| + " .. k3 .. " )"; 568 | end 569 | 570 | function cond.toString() 571 | return "c1 = " .. cond.toStringEQ(); 572 | end 573 | 574 | function cond.disambiguate(otherCond) 575 | local otherSolutionType = otherCond.getSolutionType(); 576 | 577 | if (otherSolutionType == "c1min") then 578 | local mink1, mink2, mink3, mink4 = otherCond.solve(); 579 | 580 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " >= " .. otherCond.toStringEQ() ); 581 | 582 | return helperCreateConditionAbsoluteLinearInequalityCompareLTEQ( mink1, mink2, mink3, mink4, k1, k2, k3, k4 ); 583 | elseif (otherSolutionType == "c1max") then 584 | local maxk1, maxk2, maxk3, maxk4 = otherCond.solve(); 585 | 586 | output_math_debug( "DISAMB: " .. cond.toStringEQ() .. " <= " .. otherCond.toStringEQ() ); 587 | 588 | return helperCreateConditionAbsoluteLinearInequalityCompareLTEQ( k1, k2, k3, k4, maxk1, maxk2, maxk3, maxk4 ); 589 | elseif (otherSolutionType == "c1equality") then 590 | -- Any same tight interval is good. 591 | return createConditionBoolean(true); 592 | end 593 | 594 | return false; 595 | end 596 | 597 | return cond; 598 | end -------------------------------------------------------------------------------- /mathcore/math_basiccond.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations. 2 | local tostring = tostring; 3 | 4 | -- Initialize the single global true boolean. 5 | local _true_boolean = {}; 6 | 7 | function _true_boolean.getSolutionType() 8 | return "boolean"; 9 | end 10 | 11 | function _true_boolean.solve() 12 | return true; 13 | end 14 | 15 | function _true_boolean.toString() 16 | return "true"; 17 | end 18 | 19 | -- Initialize the single global false boolean. 20 | local _false_boolean = {}; 21 | 22 | function _false_boolean.getSolutionType() 23 | return "boolean"; 24 | end 25 | 26 | function _false_boolean.solve() 27 | return false; 28 | end 29 | 30 | function _false_boolean.toString() 31 | return "false"; 32 | end 33 | 34 | -- Just a helper to fetch the correct boolean conditional object. 35 | function createConditionBoolean(value) 36 | if (value) then 37 | return _true_boolean; 38 | end 39 | 40 | return _false_boolean; 41 | end -------------------------------------------------------------------------------- /mathcore/math_condchains.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations. 2 | local ipairs = ipairs; 3 | local _G = _G; 4 | local table = table; 5 | local tinsert = table.insert; 6 | local tremove = table.remove; 7 | local tostring = tostring; 8 | local error = error; 9 | 10 | -- Global imports. 11 | local createConditionBoolean = createConditionBoolean; 12 | 13 | if not (createConditionBoolean) then 14 | error("cannot find required global imports; fatal script error."); 15 | end 16 | 17 | local function createSeparatedConditionString(vars, sep) 18 | local str = "( "; 19 | local sepStr = " " .. sep .. " "; 20 | 21 | local hasItem = false; 22 | 23 | for m,n in ipairs(vars) do 24 | if ( hasItem ) then 25 | str = str .. sepStr; 26 | end 27 | 28 | str = str .. n.toString(); 29 | 30 | hasItem = true; 31 | end 32 | 33 | str = str .. " )"; 34 | 35 | return str; 36 | end 37 | 38 | local function resolve_cond(cond) 39 | if (cond.resolve) then 40 | return cond.resolve(); 41 | end 42 | 43 | return cond; 44 | end 45 | _G.resolve_cond = resolve_cond; 46 | 47 | function createConditionAND(_initial_cond) 48 | local cond = {}; 49 | local vars = {}; 50 | local is_invalid = false; 51 | local has_only_true = false; 52 | 53 | if not (_initial_cond == nil) then 54 | has_only_true = _initial_cond; 55 | end 56 | 57 | function cond.clone() 58 | local new_cond = createConditionAND(); 59 | 60 | for m,n in ipairs(vars) do 61 | new_cond.addVar(n); 62 | end 63 | 64 | return new_cond; 65 | end 66 | 67 | function cond.reset(start_cond) 68 | vars = {}; 69 | is_invalid = false; 70 | has_only_true = false; 71 | 72 | if not (start_cond == nil) then 73 | has_only_true = start_cond; 74 | end 75 | end 76 | 77 | function cond.getSolutionType() 78 | if (has_only_true) or (is_invalid) or (#vars == 0) then 79 | return "boolean"; 80 | end 81 | 82 | if (#vars == 1) then 83 | return vars[1].getSolutionType(); 84 | end 85 | 86 | return "and-dynamic"; 87 | end 88 | 89 | function cond.solve() 90 | if (is_invalid) then 91 | return false; 92 | end 93 | 94 | if (has_only_true) then 95 | return true; 96 | end 97 | 98 | if (#vars == 0) then 99 | return false; 100 | end 101 | 102 | if (#vars == 1) then 103 | return vars[1].solve(); 104 | end 105 | end 106 | 107 | function cond.getChainType() 108 | if (has_only_true) or (is_invalid) then 109 | return "none"; 110 | end 111 | 112 | return "and"; 113 | end 114 | 115 | function cond.getVar(idx, userdata) 116 | return vars[idx]; 117 | end 118 | 119 | function cond.getVarCount() 120 | if (is_invalid) or (has_only_true) then 121 | return 0; 122 | end 123 | 124 | return #vars; 125 | end 126 | 127 | function cond.forAllVars(cb) 128 | if (is_invalid) or (has_only_true) then 129 | return; 130 | end 131 | 132 | for m,n in ipairs(vars) do 133 | cb(m, nil, n); 134 | end 135 | end 136 | 137 | -- TODO: think about this, because I had a reason to suggest removal of this function. 138 | function cond.getVars() 139 | if (is_invalid) or (has_only_true) then 140 | return {}; 141 | end 142 | 143 | return vars; 144 | end 145 | 146 | local function event_handle_newVar(var, type_of_op) 147 | -- Optimization. 148 | local solutionType = var.getSolutionType(); 149 | 150 | if (solutionType == "boolean") then 151 | if ( var.solve() == false ) then 152 | is_invalid = true; 153 | else 154 | if (type_of_op == "add-var") and (#vars == 0) and (is_invalid == false) or 155 | (type_of_op == "repl-var") and (#vars == 1) then 156 | 157 | has_only_true = true; 158 | end 159 | end 160 | 161 | return false; 162 | end 163 | 164 | has_only_true = false; 165 | return true; 166 | end 167 | 168 | function cond.addVar(var) 169 | if (is_invalid) then 170 | return; 171 | end 172 | 173 | if not (var) then 174 | error("invalid value for var", 2); 175 | end 176 | 177 | if (event_handle_newVar(var, "add-var")) then 178 | tinsert(vars, var); 179 | end 180 | end 181 | 182 | function cond.addCond(constructor, ...) 183 | if (is_invalid) then 184 | return; 185 | end 186 | 187 | cond.addVar(constructor(...)); 188 | end 189 | 190 | function cond.replaceVar(idx, userdata, replaceBy) 191 | -- userdata is unused, always pass it over as nil! 192 | 193 | if (is_invalid) then 194 | math_assert( false, "replaceVar error in and-dynamic: always false", 2 ); 195 | end 196 | 197 | if (has_only_true) then 198 | math_assert( false, "replaceVar error in and-dynamic: always true", 2 ); 199 | end 200 | 201 | local replacedVar = vars[idx]; 202 | 203 | if not (replacedVar) then 204 | math_assert( false, "replaceVar error in and-dynamic: invalid idx " .. idx .. " (has " .. #vars .. " variable[s])", 2 ); 205 | end 206 | 207 | -- TODO: maybe fix support for boolean-state. 208 | 209 | if (event_handle_newVar(replaceBy, "repl-var")) then 210 | vars[idx] = replaceBy; 211 | else 212 | if (has_only_true == false) then 213 | tremove( vars, idx ); 214 | end 215 | 216 | return "removed"; 217 | end 218 | 219 | return "ok"; 220 | end 221 | 222 | function cond.establishAND(var) 223 | if (is_invalid) then 224 | return; 225 | end 226 | 227 | local solutionType = var.getSolutionType(); 228 | 229 | if (solutionType == "and-dynamic") then 230 | var = resolve_cond(var); 231 | 232 | for m,n in ipairs(var.getVars()) do 233 | cond.establishAND(n); 234 | end 235 | else 236 | cond.addVar(var); 237 | end 238 | end 239 | 240 | function cond.pushthroughAND(var) 241 | if (is_invalid) then 242 | return; 243 | end 244 | 245 | local anyORCond = false; 246 | 247 | for m,n in ipairs(vars) do 248 | if (n.getSolutionType() == "or-dynamic") then 249 | n = resolve_cond(n); 250 | n.pushthroughAND(var); 251 | anyORCond = true; 252 | end 253 | end 254 | 255 | if not (anyORCond) then 256 | cond.establishAND(var); 257 | end 258 | end 259 | 260 | function cond.toStringEQ() 261 | if (#vars == 1) then 262 | return vars[1].toStringEQ(); 263 | elseif (#vars == 0) then 264 | return "false"; 265 | end 266 | end 267 | 268 | function cond.toString() 269 | if (is_invalid) then 270 | return "false"; 271 | elseif (has_only_true) then 272 | return "true"; 273 | elseif (#vars == 1) then 274 | return vars[1].toString(); 275 | elseif (#vars == 0) then 276 | return "false"; 277 | end 278 | 279 | return createSeparatedConditionString( vars, "AND" ); 280 | end 281 | 282 | function cond.resolve() 283 | if (is_invalid) then 284 | return createConditionBoolean(false); 285 | elseif (has_only_true) then 286 | return createConditionBoolean(true); 287 | elseif (#vars == 1) then 288 | return resolve_cond( vars[1] ); 289 | end 290 | 291 | return cond; 292 | end 293 | 294 | -- Calculates an equivalent condition which is an OR condition. 295 | -- Unused. 296 | function cond.spliceOnce() 297 | local rootCond = createConditionOR(); 298 | 299 | if (has_only_true) or (is_invalid) then 300 | return rootCond; 301 | end 302 | 303 | for m,n in ipairs(vars) do 304 | rootCond.establishAND(n); 305 | end 306 | 307 | return rootCond; 308 | end 309 | 310 | return cond; 311 | end 312 | 313 | local function travel_and_line(andcond, cb) 314 | if (andcond.getSolutionType() == "and-dynamic") then 315 | andcond = resolve_cond(andcond); 316 | 317 | for m,n in ipairs(andcond.getVars()) do 318 | travel_and_line(n, cb); 319 | end 320 | else 321 | cb(andcond); 322 | end 323 | end 324 | _G.travel_and_line = travel_and_line; 325 | 326 | local function for_all_cases(cond, cb) 327 | if (cond.getSolutionType() == "or-dynamic") then 328 | cond = resolve_cond(cond); 329 | for m,n in ipairs(cond.getVars()) do 330 | for_all_cases(n, cb); 331 | end 332 | else 333 | cb(cond); 334 | end 335 | end 336 | _G.for_all_cases = for_all_cases; 337 | 338 | function createConditionOR(_initial_cond) 339 | local cond = {}; 340 | local vars = vars_in or {}; 341 | local is_valid_straight = false; 342 | local has_always_false = false; 343 | 344 | if not (_initial_cond == nil) then 345 | if (_initial_cond) then 346 | is_valid_straight = true; 347 | else 348 | has_always_false = true; 349 | end 350 | end 351 | 352 | function cond.clone() 353 | local newcond = createConditionOR(); 354 | 355 | for m,n in ipairs(vars) do 356 | newcond.addVar(n); 357 | end 358 | 359 | return newcond; 360 | end 361 | 362 | function cond.getSolutionType() 363 | if (is_valid_straight) or (has_always_false) then 364 | return "boolean"; 365 | end 366 | 367 | if (#vars == 1) then 368 | return vars[1].getSolutionType(); 369 | end 370 | 371 | return "or-dynamic"; 372 | end 373 | 374 | function cond.solve() 375 | if (is_valid_straight) then 376 | return true; 377 | elseif (has_always_false) then 378 | return false; 379 | elseif (#vars == 1) then 380 | return vars[1].solve(); 381 | end 382 | end 383 | 384 | function cond.getChainType() 385 | if (is_valid_straight) or (has_always_false) then 386 | return "none"; 387 | end 388 | 389 | return "or"; 390 | end 391 | 392 | function cond.reset() 393 | is_valid_straight = false; 394 | vars = {}; 395 | end 396 | 397 | function cond.getVar(idx, userdata) 398 | return vars[idx]; 399 | end 400 | 401 | function cond.getVarCount() 402 | if (is_valid_straight) or (has_always_false) then 403 | return 0; 404 | end 405 | 406 | return #vars; 407 | end 408 | 409 | function cond.forAllVars(cb) 410 | if (is_valid_straight) or (has_always_false) then 411 | return; 412 | end 413 | 414 | for m,n in ipairs(vars) do 415 | cb(m, nil, n); 416 | end 417 | end 418 | 419 | function cond.getVars() 420 | if (is_valid_straight) or (has_always_false) then 421 | return {}; 422 | end 423 | 424 | return vars; 425 | end 426 | 427 | local function event_handle_newVar(var, type_of_op) 428 | -- Optimization. 429 | local solutionType = var.getSolutionType(); 430 | 431 | if (solutionType == "boolean") then 432 | if (var.solve() == true) then 433 | is_valid_straight = true; 434 | has_always_false = false; 435 | else 436 | if (type_of_op == "add-var") and (#vars == 0) or 437 | (type_of_op == "repl-var") and (#vars == 1) then 438 | 439 | has_always_false = true; 440 | end 441 | end 442 | 443 | return false; 444 | end 445 | 446 | has_always_false = false; 447 | return true; 448 | end 449 | 450 | function cond.addVar(var) 451 | if (is_valid_straight) then 452 | return; 453 | end 454 | 455 | if (event_handle_newVar(var, "add-var")) then 456 | tinsert(vars, var); 457 | end 458 | end 459 | 460 | function cond.addCond(constructor, ...) 461 | if (is_valid_straight) then 462 | return; 463 | end 464 | 465 | cond.addVar(constructor(...)); 466 | end 467 | 468 | function cond.replaceVar(idx, userdata, replaceBy) 469 | -- userdata is unused, always pass as nil! 470 | 471 | if (is_valid_straight) then 472 | math_assert( false, "replaceVar error in or-dynamic: always true", 2 ); 473 | end 474 | 475 | if (has_always_false) then 476 | math_assert( false, "replaceVar error in or-dynamic: always false", 2 ); 477 | end 478 | 479 | local replacedVar = vars[idx]; 480 | 481 | if not (replacedVar) then 482 | math_assert( false, "replaceVar error in or-dynamic: invalid idx " .. idx .. " (has " .. #vars .. " variable[s])", 2 ); 483 | end 484 | 485 | if (event_handle_newVar(replaceBy, "repl-var")) then 486 | vars[idx] = replaceBy; 487 | else 488 | if (has_always_false == false) then 489 | tremove(vars, idx); 490 | end 491 | 492 | return "removed"; 493 | end 494 | 495 | return "ok"; 496 | end 497 | 498 | function cond.toString() 499 | if (is_valid_straight) then 500 | return "true"; 501 | elseif (has_always_false) then 502 | return "false"; 503 | elseif (#vars == 1) then 504 | return vars[1].toString(); 505 | end 506 | 507 | return createSeparatedConditionString( vars, "OR" ); 508 | end 509 | 510 | function cond.resolve() 511 | if (is_valid_straight) then 512 | return createConditionBoolean(true); 513 | elseif (has_always_false) then 514 | return createConditionBoolean(false); 515 | elseif (#vars == 1) then 516 | return resolve_cond( vars[1] ); 517 | end 518 | 519 | return cond; 520 | end 521 | 522 | function cond.toStringEQ() 523 | if (#vars == 1) then 524 | return vars[1].toStringEQ(); 525 | end 526 | end 527 | 528 | local function conjunct_cond(cond, conjitem) 529 | local solutionType = cond.getSolutionType(); 530 | 531 | if (solutionType == "or-dynamic") then 532 | cond.establishAND(conjitem); 533 | return cond; 534 | end 535 | 536 | if (solutionType == "and-dynamic") then 537 | travel_and_line(conjitem, 538 | function(n) 539 | cond.addVar(n); 540 | end 541 | ); 542 | 543 | return cond; 544 | end 545 | 546 | local newcond = createConditionAND(); 547 | newcond.addVar(cond); 548 | newcond.establishAND(conjitem); 549 | return newcond; 550 | end 551 | 552 | local function conjunct_cond_new(cond, conjitem) 553 | cond = resolve_cond(cond); 554 | 555 | if (cond.clone) then 556 | return conjunct_cond(cond.clone(), conjitem); 557 | end 558 | 559 | return conjunct_cond(cond, conjitem); 560 | end 561 | 562 | function cond.distributeAND(otherCond) 563 | if (has_always_false) then 564 | return; 565 | end 566 | 567 | local solutionType = otherCond.getSolutionType(); 568 | 569 | if (solutionType == "or-dynamic") then 570 | otherCond = resolve_cond(otherCond); 571 | 572 | local oldVars = vars; 573 | cond.reset(); 574 | 575 | for _,orCond in ipairs(oldVars) do 576 | for_all_cases(otherCond, 577 | function(subOrCond) 578 | cond.addVar(conjunct_cond_new(orCond, subOrCond)); 579 | end 580 | ); 581 | end 582 | else 583 | cond.establishAND(otherCond); 584 | end 585 | end 586 | 587 | function cond.establishAND(otherCond) 588 | if (has_always_false) then 589 | return; 590 | end 591 | 592 | local solutionType = otherCond.getSolutionType(); 593 | 594 | if (solutionType == "boolean") then 595 | if (otherCond.solve() == false) then 596 | cond.reset(false); 597 | end 598 | else 599 | if (#vars == 0) then 600 | cond.addVar( otherCond ); 601 | else 602 | for m,subvar in ipairs(vars) do 603 | vars[m] = conjunct_cond_new( subvar, otherCond ); 604 | end 605 | end 606 | end 607 | end 608 | 609 | function cond.pushthroughAND(otherCond) 610 | if (#vars == 0) then 611 | cond.establishAND(otherCond); 612 | return; 613 | end 614 | 615 | local m = 1; 616 | 617 | while ( m <= #vars ) do 618 | local orVar = vars[m]; 619 | local solutionType = orVar.getSolutionType(); 620 | 621 | local should_advance = true; 622 | 623 | if (solutionType == "and-dynamic") or (solutionType == "or-dynamic") then 624 | orVar = resolve_cond( orVar ); 625 | orVar = orVar.clone(); 626 | orVar.pushthroughAND(otherCond); 627 | should_advance = not ( cond.replaceVar( m, orVar ) == "removed" ); 628 | else 629 | local replRet = cond.replaceVar( m, conjunct_cond( orVar, otherCond ) ); 630 | should_advance = not ( replRet == "removed" ); 631 | end 632 | 633 | if (cond.getChainType() == "none") then 634 | break; 635 | end 636 | 637 | if (should_advance) then 638 | m = m + 1; 639 | end 640 | end 641 | end 642 | 643 | return cond; 644 | end -------------------------------------------------------------------------------- /mathcore/math_utilities.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations. 2 | local math = math; 3 | local mabs = math.abs; 4 | local mfloor = math.floor; 5 | local type = type; 6 | local ipairs = ipairs; 7 | local tostring = tostring; 8 | local _G = _G; 9 | 10 | -- There is a long history of CPU's not accurately implementing floating-point mathematics. 11 | -- In the end you could only trust the "<" and ">" operators to be accurate. 12 | -- That is why these functions were used. 13 | local _epsilon = 0.000001; 14 | 15 | local function _math_leq(a, b) 16 | return ( a - _epsilon <= b ); 17 | end 18 | _G._math_leq = _math_leq; 19 | 20 | local function _math_geq(a, b) 21 | return ( a + _epsilon >= b ); 22 | end 23 | _G._math_geq = _math_geq; 24 | 25 | local function _math_eq(a, b) 26 | if (type(a) == "number") and (type(b) == "number") then 27 | local absdiff = mabs(a - b); 28 | 29 | return ( absdiff < _epsilon ); 30 | end 31 | 32 | return ( a == b ); 33 | end 34 | _G._math_eq = _math_eq; 35 | 36 | local function mcs_multsum(values, names) 37 | local multsumstr = ""; 38 | local was_all_zero = true; 39 | 40 | for m,n in ipairs(values) do 41 | local addpart = ""; 42 | 43 | if not (n == 0) then 44 | if not ( n == 1 ) and not ( n == -1 ) then 45 | if ( n < 0 ) or not ( n == mfloor(n) ) then 46 | addpart = "(" .. n .. ")"; 47 | else 48 | addpart = tostring( n ); 49 | end 50 | elseif ( n == -1 ) then 51 | addpart = "-"; 52 | elseif ( n == 1 ) then 53 | addpart = ""; 54 | end 55 | 56 | local theName = names[m]; 57 | 58 | if (theName) then 59 | if not ( addpart == "" ) and not ( addpart == "-") then 60 | addpart = addpart .. "*"; 61 | end 62 | 63 | addpart = addpart .. theName; 64 | elseif (addpart == "") then 65 | addpart = "1"; 66 | elseif (addpart == "-") then 67 | addpart = "(-1)"; 68 | end 69 | end 70 | 71 | if not (addpart == "") then 72 | was_all_zero = false; 73 | 74 | if not (multsumstr == "") then 75 | multsumstr = multsumstr .. " + "; 76 | end 77 | 78 | multsumstr = multsumstr .. addpart; 79 | end 80 | end 81 | 82 | if (was_all_zero) then 83 | return "0"; 84 | end 85 | 86 | return multsumstr; 87 | end 88 | _G.mcs_multsum = mcs_multsum; 89 | 90 | local cfg_output_trivial_calc = false; 91 | 92 | -- MATH DEBUG: create a file where we output all stuff of calculation. 93 | if (fileExists( "math_debug.txt")) then 94 | fileDelete( "math_debug.txt" ); 95 | end 96 | 97 | local debug_file = false; 98 | 99 | local function stop_handler() 100 | if (debug_file) then 101 | fileFlush(debug_file); 102 | fileClose(debug_file); 103 | end 104 | end 105 | 106 | if ( localplayer ) then 107 | addEventHandler( "onClientResourceStop", root, stop_handler ); 108 | else 109 | addEventHandler( "onResourceStop", root, stop_handler ); 110 | end 111 | 112 | local function output_math_debug(str) 113 | if not (debug_file) then 114 | debug_file = fileCreate( "math_debug.txt" ); 115 | end 116 | 117 | if (debug_file) then 118 | fileWrite(debug_file, str .. "\n"); 119 | fileFlush(debug_file); 120 | end 121 | 122 | --outputDebugString( str ); 123 | --outputConsole( "#" .. str ); 124 | end 125 | _G.output_math_debug = output_math_debug; 126 | 127 | local function math_assert(cond, string, error_stack_off) 128 | if not (cond) then 129 | output_math_debug(string); 130 | end 131 | 132 | if not (cond) then 133 | if not (error_stack_off) then 134 | error_stack_off = 1; 135 | end 136 | 137 | error( string, error_stack_off + 1 ) 138 | end 139 | end 140 | _G.math_assert = math_assert; -------------------------------------------------------------------------------- /meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /progress_feedback_client.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | local is_currently_processing = false; 3 | local server_status_percentage = 0; 4 | local server_status_msg = false; 5 | 6 | addEventHandler( "onClientRender", root, function() 7 | -- Draw the progress UI if the server is doing something. 8 | if not (is_currently_processing) then return false; end; 9 | 10 | local screenWidth, screenHeight = guiGetScreenSize(); 11 | 12 | local box_start_x = screenWidth * 0.25; 13 | local box_start_y = screenHeight - 200; 14 | 15 | local box_width = ( screenWidth * 0.5 ); 16 | local box_height = 150; 17 | 18 | dxDrawRectangle( box_start_x, box_start_y, box_width, box_height, 0x80000000 ); 19 | 20 | local status_text = "Status: "; 21 | 22 | if (server_status_msg) then 23 | status_text = status_text .. server_status_msg; 24 | else 25 | status_text = status_text .. "undefined"; 26 | end 27 | 28 | dxDrawText( status_text, box_start_x + 5, box_start_y + 5 ); 29 | 30 | local bar_bg_x = box_start_x + 10; 31 | local bar_bg_y = box_start_y + 35; 32 | local bar_bg_width = box_width - 20; 33 | local bar_bg_height = box_height - 60; 34 | dxDrawRectangle( box_start_x + 10, box_start_y + 35, box_width - 20, box_height - 60, 0xFFFFFFFF ); 35 | 36 | local cur_perc = math.min(math.max(server_status_percentage, 0), 1); 37 | 38 | local width_of_perc = ( bar_bg_width - 6 ) * cur_perc; 39 | 40 | dxDrawRectangle( bar_bg_x + 3, bar_bg_y + 3, width_of_perc, bar_bg_height - 6, 0xFF0000FF ); 41 | 42 | local mid_bar_x = ( bar_bg_x + bar_bg_width / 2 ); 43 | local mid_bar_y = ( bar_bg_y + bar_bg_height / 2 ); 44 | 45 | local use_font = "default"; 46 | 47 | local perc_text = tostring(math.floor(server_status_percentage * 100)) .. " %"; 48 | 49 | local text_scale = 3; 50 | local text_height = dxGetFontHeight(text_scale, use_font); 51 | 52 | local text_width = dxGetTextWidth(perc_text, text_scale); 53 | 54 | local center_text_x = ( mid_bar_x - text_width / 2 ); 55 | local center_text_y = ( mid_bar_y - text_height / 2 ); 56 | 57 | dxDrawText( perc_text, center_text_x, center_text_y, center_text_x, center_text_y, 0xFF00FF00, text_scale ); 58 | end 59 | ); 60 | 61 | addEvent("onServerProgressStart", true); 62 | addEventHandler("onServerProgressStart", root, function(msg) 63 | server_status_msg = msg; 64 | server_status_percentage = 0; 65 | 66 | is_currently_processing = true; 67 | end 68 | ); 69 | 70 | addEvent("onServerProgressUpdate", true); 71 | addEventHandler("onServerProgressUpdate", root, function(perc, msg) 72 | server_status_msg = msg; 73 | server_status_percentage = perc; 74 | end 75 | ); 76 | 77 | addEvent("onServerProgressEnd", true); 78 | addEventHandler("onServerProgressEnd", root, function() 79 | is_currently_processing = false; 80 | end 81 | ); -------------------------------------------------------------------------------- /progress_feedback_server.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | local currently_running_task = false; 3 | local completion_percentage = 0; 4 | local status_msg = ""; 5 | 6 | function spawnTask(routine, ...) 7 | if (currently_running_task) then return false; end; 8 | 9 | completion_percentage = 0; 10 | status_msg = ""; 11 | 12 | local args = { ... }; 13 | 14 | triggerClientEvent("onServerProgressStart", root, status_msg); 15 | 16 | currently_running_task = createThread( 17 | function(thread) 18 | routine(thread, unpack(args)); 19 | 20 | -- Tell the client that we finished. 21 | triggerClientEvent("onServerProgressEnd", root); 22 | 23 | -- We finished running. 24 | currently_running_task = false; 25 | end 26 | ); 27 | 28 | currently_running_task.sustime(50); 29 | 30 | return true; 31 | end 32 | 33 | function taskUpdate(percentage, message, fastUpdateClient) 34 | if (currently_running_task) then 35 | if (percentage) then 36 | completion_percentage = percentage; 37 | end 38 | 39 | if (message) then 40 | status_msg = message; 41 | end 42 | 43 | local doSendToClient = true; 44 | 45 | if (fastUpdateClient == false) then 46 | doSendToClient = currently_running_task.yield(); 47 | end 48 | 49 | if (doSendToClient) then 50 | -- Send an update to the client. 51 | triggerClientEvent("onServerProgressUpdate", root, completion_percentage, status_msg); 52 | end 53 | 54 | if not (fastUpdateClient == false) then 55 | currently_running_task.yield(); 56 | end 57 | end 58 | end -------------------------------------------------------------------------------- /reqdebugdraw_client.lua: -------------------------------------------------------------------------------- 1 | local do_debug_triangle = false; 2 | 3 | local triangle = createPlane( 4 | createVector(0, 0, 5), 5 | createVector(25, 0, 0), 6 | createVector(0, 0, 15) 7 | ); 8 | 9 | local frustum_pos = createVector(0, 0, 0); 10 | local frustum_right = createVector(0, 0, 0); 11 | local frustum_up = createVector(0, 0, 0); 12 | local frustum_front = createVector(0, 0, 0); 13 | 14 | local frustum = createViewFrustum( 15 | frustum_pos, 16 | frustum_right, 17 | frustum_up, 18 | frustum_front 19 | ); 20 | 21 | addEventHandler("onClientRender", root, 22 | function() 23 | if not (do_debug_triangle) then return; end; 24 | 25 | local triPos = triangle.getPos(); 26 | local triU = triangle.getU(); 27 | local triV = triangle.getV(); 28 | 29 | local vert1 = { 30 | triPos.getX(), 31 | triPos.getY(), 32 | triPos.getZ(), 33 | tocolor(255, 255, 255) 34 | }; 35 | 36 | local vert2 = { 37 | triPos.getX() + triU.getX(), 38 | triPos.getY() + triU.getY(), 39 | triPos.getZ() + triU.getZ(), 40 | tocolor(255, 255, 255) 41 | }; 42 | 43 | local vert3 = { 44 | triPos.getX() + triV.getX(), 45 | triPos.getY() + triV.getY(), 46 | triPos.getZ() + triV.getZ(), 47 | tocolor(255, 255, 255) 48 | }; 49 | 50 | dxDrawPrimitive3D("trianglelist", false, vert1, vert2, vert3); 51 | 52 | -- Check whether the triangle is on screen. 53 | local camMat = getElementMatrix(getCamera()); 54 | local camPos = camMat[4]; 55 | local camRight = camMat[1]; 56 | local camFront = camMat[2]; 57 | local camUp = camMat[3]; 58 | local farClip = getFarClipDistance(); 59 | 60 | local cam_frontX = camFront[1] * farClip; 61 | local cam_frontY = camFront[2] * farClip; 62 | local cam_frontZ = camFront[3] * farClip; 63 | 64 | local sW, sH = guiGetScreenSize(); 65 | 66 | local s_ratio = sW / sH; 67 | 68 | local _, _, _, _, _, _, _, fov = getCameraMatrix(); 69 | local fovRad = math.rad(fov/2); 70 | 71 | local cam_side_dist = farClip * math.tan(fovRad); 72 | local cam_up_dist = cam_side_dist / s_ratio; 73 | 74 | local cam_rightX = camRight[1] * cam_side_dist; 75 | local cam_rightY = camRight[2] * cam_side_dist; 76 | local cam_rightZ = camRight[3] * cam_side_dist; 77 | 78 | local cam_upX = camUp[1] * cam_up_dist; 79 | local cam_upY = camUp[2] * cam_up_dist; 80 | local cam_upZ = camUp[3] * cam_up_dist; 81 | 82 | frustum_pos.setX(camPos[1]); 83 | frustum_pos.setY(camPos[2]); 84 | frustum_pos.setZ(camPos[3]); 85 | 86 | frustum_right.setX(cam_rightX); 87 | frustum_right.setY(cam_rightY); 88 | frustum_right.setZ(cam_rightZ); 89 | 90 | frustum_up.setX(cam_upX); 91 | frustum_up.setY(cam_upY); 92 | frustum_up.setZ(cam_upZ); 93 | 94 | frustum_front.setX(cam_frontX); 95 | frustum_front.setY(cam_frontY); 96 | frustum_front.setZ(cam_frontZ); 97 | 98 | local inter = frustum.intersectWithTrianglePlane(triangle); 99 | 100 | dxDrawText("is intersecting: " .. tostring(not (inter == false)), 100, 300); 101 | 102 | dxDrawText("side_dist: " .. cam_side_dist, 100, 320); 103 | dxDrawText("up_dist: " .. cam_up_dist, 100, 340); 104 | dxDrawText("far_clip: " .. farClip, 100, 360); 105 | dxDrawText("FOV: " .. fov, 100, 380); 106 | dxDrawText("aspect ratio: " .. s_ratio, 100, 400); 107 | dxDrawText("camPos: " .. camPos[1] .. ", " .. camPos[2] .. ", " .. camPos[3], 100, 420); 108 | dxDrawText("camRight: " .. cam_rightX .. ", " .. cam_rightY .. ", " .. cam_rightZ, 100, 440); 109 | dxDrawText("camUp: " .. cam_upX .. ", " .. cam_upY .. ", " .. cam_upZ, 100, 460); 110 | dxDrawText("camFront: " .. cam_frontX .. ", " .. cam_frontY .. ", " .. cam_frontZ, 100, 480); 111 | end 112 | ); 113 | 114 | local function draw_triangle_on_server(frustum, triangle) 115 | triggerServerEvent("request_drawtri", root, 116 | triangle.getPos().getX(), 117 | triangle.getPos().getY(), 118 | triangle.getPos().getZ(), 119 | triangle.getU().getX(), 120 | triangle.getU().getY(), 121 | triangle.getU().getZ(), 122 | triangle.getV().getX(), 123 | triangle.getV().getY(), 124 | triangle.getV().getZ(), 125 | frustum_pos.getX(), 126 | frustum_pos.getY(), 127 | frustum_pos.getZ(), 128 | frustum_right.getX(), 129 | frustum_right.getY(), 130 | frustum_right.getZ(), 131 | frustum_up.getX(), 132 | frustum_up.getY(), 133 | frustum_up.getZ(), 134 | frustum_front.getX(), 135 | frustum_front.getY(), 136 | frustum_front.getZ() 137 | ); 138 | end 139 | 140 | addCommandHandler("tridraw", 141 | function() 142 | do_debug_triangle = not do_debug_triangle; 143 | end 144 | ); 145 | 146 | addCommandHandler("draw_curscene", 147 | function() 148 | draw_triangle_on_server(frustum, triangle); 149 | end 150 | ); -------------------------------------------------------------------------------- /reqdebugdraw_server.lua: -------------------------------------------------------------------------------- 1 | local function task_draw_current_scene(thread, tri_pos_x, tri_pos_y, tri_pos_z, tri_u_x, tri_u_y, tri_u_z, tri_v_x, tri_v_y, tri_v_z, 2 | frustum_pos_x, frustum_pos_y, frustum_pos_z, frustum_right_x, frustum_right_y, frustum_right_z, 3 | frustum_up_x, frustum_up_y, frustum_up_z, frustum_front_x, frustum_front_y, frustum_front_z) 4 | 5 | -- Write the triangle into a file on the server for debug purposes. 6 | do 7 | local trifile = "debug_tri_draw.lua"; 8 | local tricode = 9 | "local triangle = createPlane(\n" .. 10 | " createVector(" .. tri_pos_x .. ", " .. tri_pos_y .. ", " .. tri_pos_z .. "),\n" .. 11 | " createVector(" .. tri_u_x .. ", " .. tri_u_y .. ", " .. tri_u_z .. "),\n" .. 12 | " createVector(" .. tri_v_x .. ", " .. tri_v_y .. ", " .. tri_v_z .. ")\n" .. 13 | ");\n" .. 14 | "local frustum = createViewFrustum(\n" .. 15 | " createVector(" .. frustum_pos_x .. ", " .. frustum_pos_y .. ", " .. frustum_pos_z .. "),\n" .. 16 | " createVector(" .. frustum_right_x .. ", " .. frustum_right_y .. ", " .. frustum_right_z .. "),\n" .. 17 | " createVector(" .. frustum_up_x .. ", " .. frustum_up_y .. ", " .. frustum_up_z .. "),\n" .. 18 | " createVector(" .. frustum_front_x .. ", " .. frustum_front_y .. ", " .. frustum_front_z .. ")\n" .. 19 | ");\n" .. 20 | "local primType = \"tri\";"; 21 | 22 | if (fileExists(trifile)) then 23 | fileDelete(trifile); 24 | end 25 | 26 | local debugFile = fileCreate(trifile); 27 | 28 | if (debugFile) then 29 | fileWrite(debugFile, tricode); 30 | fileClose(debugFile); 31 | end 32 | end 33 | 34 | local triangle = createPlane( 35 | createVector(tri_pos_x, tri_pos_y, tri_pos_z), 36 | createVector(tri_u_x, tri_u_y, tri_u_z), 37 | createVector(tri_v_x, tri_v_y, tri_v_z) 38 | ); 39 | 40 | local frustum = createViewFrustum( 41 | createVector(frustum_pos_x, frustum_pos_y, frustum_pos_z), 42 | createVector(frustum_right_x, frustum_right_y, frustum_right_z), 43 | createVector(frustum_up_x, frustum_up_y, frustum_up_z), 44 | createVector(frustum_front_x, frustum_front_y, frustum_front_z) 45 | ); 46 | 47 | local bbuf = create_backbuffer(640, 480, 255, 255, 0, 50); 48 | local dbuf = createDepthBuffer(640, 480, 1); 49 | 50 | local time_start = getTickCount(); 51 | 52 | do 53 | local gotToDraw, numDrawn, numSkipped = draw_plane_on_bbuf(frustum, bbuf, dbuf, triangle, true, "tri", true); 54 | 55 | if ( gotToDraw ) then 56 | outputDebugString( "drawn " .. numDrawn .. " pixels (skipped " .. numSkipped .. ")" ); 57 | end 58 | end 59 | 60 | local time_end = getTickCount(); 61 | local ms_diff = ( time_end - time_start ); 62 | 63 | outputDebugString( "render time: " .. ms_diff .. "ms" ); 64 | 65 | taskUpdate( 1, "creating backbuffer color composition string" ); 66 | 67 | local bbuf_width_ushort = num_to_ushort_bytes( bbuf.width ); 68 | local bbuf_height_ushort = num_to_ushort_bytes( bbuf.height ); 69 | 70 | local pixels_str = table.concat(bbuf.items); 71 | 72 | local bbuf_string = 73 | pixels_str .. 74 | ( bbuf_width_ushort .. 75 | bbuf_height_ushort ); 76 | 77 | taskUpdate( false, "sending backbuffer to clients (render time: " .. ms_diff .. "ms)" ); 78 | 79 | local players = getElementsByType("player"); 80 | 81 | for m,n in ipairs(players) do 82 | triggerClientEvent(n, "onServerTransmitImage", root, bbuf_string); 83 | end 84 | 85 | outputDebugString("sent backbuffer to clients"); 86 | end 87 | 88 | addEvent("request_drawtri", true); 89 | addEventHandler("request_drawtri", root, 90 | function(tri_pos_x, tri_pos_y, tri_pos_z, tri_u_x, tri_u_y, tri_u_z, tri_v_x, tri_v_y, tri_v_z, 91 | frustum_pos_x, frustum_pos_y, frustum_pos_z, 92 | frustum_right_x, frustum_right_y, frustum_right_z, 93 | frustum_up_x, frustum_up_y, frustum_up_z, 94 | frustum_front_x, frustum_front_y, frustum_front_z) 95 | 96 | spawnTask(task_draw_current_scene, tri_pos_x, tri_pos_y, tri_pos_z, tri_u_x, tri_u_y, tri_u_z, tri_v_x, tri_v_y, tri_v_z, 97 | frustum_pos_x, frustum_pos_y, frustum_pos_z, 98 | frustum_right_x, frustum_right_y, frustum_right_z, 99 | frustum_up_x, frustum_up_y, frustum_up_z, 100 | frustum_front_x, frustum_front_y, frustum_front_z 101 | ); 102 | end 103 | ); -------------------------------------------------------------------------------- /rw_server.lua: -------------------------------------------------------------------------------- 1 | -- Created by (c)The_GTA. 2 | -- TEST: 3 | local dffname = "gfriend.dff"; 4 | local clumpStream = fileOpen(dffname); 5 | 6 | if not (clumpStream) then 7 | outputDebugString( "failed to open " .. dffname ); 8 | return; 9 | end 10 | 11 | local dff, err = rwReadClump(clumpStream); 12 | 13 | fileClose(clumpStream); 14 | 15 | if not (dff) then 16 | if (err) then 17 | outputDebugString( "DFF LOAD ERROR: " .. err ); 18 | else 19 | outputDebugString( "unknown DFF load error" ); 20 | end 21 | elseif (false) then 22 | local num_frames = #dff.framelist.frames; 23 | 24 | outputDebugString( "num frames: " .. num_frames ); 25 | 26 | local geom = dff.geomlist[1]; 27 | local mt = geom.morphTargets[1]; 28 | 29 | outputDebugString( "radius: " .. geom.morphTargets[1].sphere.r ); 30 | outputDebugString( "x: " .. mt.sphere.x .. ", y: " .. mt.sphere.y .. ", z: " .. mt.sphere.z ); 31 | end -------------------------------------------------------------------------------- /rw_shared.lua: -------------------------------------------------------------------------------- 1 | -- RenderWare stream parsing implementations by (c)The_GTA. 2 | -- Since Lua cannot process data as efficiently as native languages, the implementations 3 | -- in this file are optimized for comfort instead of performance. 4 | -- We do not implement reading "broken RW files" with their chunk sizes borked; fix them with 5 | -- public tools before loading them. 6 | local RW_DEBUG = true; 7 | 8 | local function _read_byte(filePtr) 9 | local numbytes = fileRead(filePtr, 1); 10 | 11 | if not (numbytes) or (#numbytes < 1) then 12 | return false; 13 | end 14 | 15 | return string.byte(numbytes, 1); 16 | end 17 | 18 | local function _read_uint16(filePtr) 19 | local numbytes = fileRead(filePtr, 2); 20 | 21 | if not (numbytes) or (#numbytes < 2) then 22 | return false; 23 | end 24 | 25 | return ( string.byte(numbytes, 2) * 0x0100 + string.byte(numbytes, 1) ); 26 | end 27 | 28 | local function _read_uint32(filePtr) 29 | local numbytes = fileRead(filePtr, 4); 30 | 31 | if not (numbytes) or (#numbytes < 4) then 32 | return false; 33 | end 34 | 35 | return ( string.byte(numbytes, 4) * 0x01000000 + string.byte(numbytes, 3) * 0x00010000 + string.byte(numbytes, 2) * 0x00000100 + string.byte(numbytes, 1) ); 36 | end 37 | 38 | local function _read_float32(filePtr) 39 | local bytes = fileRead(filePtr, 4); 40 | 41 | if not (bytes) or (#bytes < 4) then return false; end; 42 | 43 | -- Vendor function. 44 | return bytes2float(bytes); 45 | end 46 | 47 | local function _read_rwversion(filePtr) 48 | local verBytes = fileRead(filePtr, 4); 49 | 50 | if not (verBytes) or (#verBytes < 4) then return false; end; 51 | 52 | local vb1 = string.byte(verBytes, 1); 53 | local vb2 = string.byte(verBytes, 2); 54 | local vb3 = string.byte(verBytes, 3); 55 | local vb4 = string.byte(verBytes, 4); 56 | 57 | local rwLibMajor = bitExtract(vb4, 6, 2); 58 | local rwRelMajor = bitExtract(vb4, 2, 4); 59 | local rwRelMinor = bitExtract(vb4, 0, 2) * 0x0F + bitExtract(vb3, 6, 2); 60 | local rwBinFmtRev = bitExtract(vb3, 0, 6); 61 | 62 | local buildNumber = ( vb2 * 0x0100 + vb1 ); 63 | 64 | local verInfo = {}; 65 | verInfo.libMajor = rwLibMajor + 3; 66 | verInfo.relMajor = rwRelMajor; 67 | verInfo.relMinor = rwRelMinor; 68 | verInfo.binFmtRev = rwBinFmtRev; 69 | verInfo.buildNumber = buildNumber; 70 | 71 | return verInfo; 72 | end 73 | 74 | local function rwReadChunkHeader(filePtr) 75 | local chunkType = _read_uint32(filePtr); 76 | local chunkSize = _read_uint32(filePtr); 77 | local chunkVersion = _read_rwversion(filePtr); 78 | 79 | if not (chunkType) or not (chunkSize) or not (chunkVersion) then return false; end; 80 | 81 | local chunk = {}; 82 | chunk.type = chunkType; 83 | chunk.version = chunkVersion; 84 | chunk.size = chunkSize; 85 | 86 | return chunk; 87 | end 88 | 89 | local function readPackedVector(filePtr) 90 | local vec = {}; 91 | 92 | vec.x = _read_float32(filePtr); 93 | vec.y = _read_float32(filePtr); 94 | vec.z = _read_float32(filePtr); 95 | 96 | return vec; 97 | end 98 | 99 | local function readPackedMatrix(filePtr) 100 | local matrix = {}; 101 | 102 | matrix.right = readPackedVector(filePtr); 103 | matrix.up = readPackedVector(filePtr); 104 | matrix.front = readPackedVector(filePtr); 105 | 106 | if not (matrix.right) or not (matrix.up) or not (matrix.front) then return false; end; 107 | 108 | return matrix; 109 | end 110 | 111 | local function readExtensions(filePtr) 112 | local chunkHeader = rwReadChunkHeader(filePtr); 113 | 114 | if not (chunkHeader) then 115 | return false, "failed to read chunk header"; 116 | end 117 | 118 | if not (chunkHeader.type == 0x03) then 119 | return false, "not an extension (got " .. chunkHeader.type .. ")"; 120 | end 121 | 122 | -- We just skip extensions, for now. 123 | local curSeek = fileGetPos(filePtr); 124 | 125 | fileSetPos(filePtr, curSeek + chunkHeader.size ); 126 | 127 | return {}; 128 | end 129 | 130 | function rwCreateFrame() 131 | local frame = {}; 132 | 133 | return frame; 134 | end 135 | 136 | local function readFrameList(filePtr) 137 | local chunkHeader = rwReadChunkHeader(filePtr); 138 | 139 | if not (chunkHeader) then 140 | return false, "failed to read chunk header"; 141 | end 142 | 143 | if not (chunkHeader.type == 0xE) then 144 | return false, "not a frame list"; 145 | end 146 | 147 | local structHeader = rwReadChunkHeader(filePtr); 148 | 149 | if not (structHeader) then 150 | return false, "failed to read struct chunk header"; 151 | end 152 | 153 | if not (structHeader.type == 1) then 154 | return false, "not a struct chunk"; 155 | end 156 | 157 | local num_frames = _read_uint32(filePtr); 158 | 159 | if not (num_frames) then 160 | return false, "failed to read number of frames"; 161 | end 162 | 163 | local frames = {}; 164 | 165 | for iter=1,num_frames,1 do 166 | local rotation_mat = readPackedMatrix(filePtr); 167 | 168 | if not (rotation_mat) then 169 | return false, "failed to read rotation matrix"; 170 | end 171 | 172 | local translation = readPackedVector(filePtr); 173 | 174 | if not (translation) then 175 | return false, "failed to read translation offset"; 176 | end 177 | 178 | local frame_idx = _read_uint32(filePtr); 179 | 180 | if not (frame_idx) then 181 | return false, "failed to read frame index"; 182 | end 183 | 184 | local matrix_flags = _read_uint32(filePtr); 185 | 186 | if not (matrix_flags) then 187 | return false, "failed to read matrix flags"; 188 | end 189 | 190 | local frame = rwCreateFrame(); 191 | frame.rotation_mat = rotation_mat; 192 | frame.idx = frame_idx; 193 | frame.translation = translation; 194 | frame.mat_flags = matrix_flags; 195 | 196 | frames[iter] = frame; 197 | end 198 | 199 | for n=1,num_frames,1 do 200 | local extensions, err = readExtensions(filePtr); 201 | 202 | if not (extensions) then 203 | return false, "failed to read extensions: " .. err; 204 | end 205 | 206 | frames[n].extensions = extensions; 207 | end 208 | 209 | local framelist = {}; 210 | framelist.frames = frames; 211 | 212 | return framelist; 213 | end 214 | 215 | local function readBoundingSphere(filePtr) 216 | local sphere = {}; 217 | sphere.x = _read_float32(filePtr); 218 | sphere.y = _read_float32(filePtr); 219 | sphere.z = _read_float32(filePtr); 220 | sphere.r = _read_float32(filePtr); 221 | 222 | if not (sphere.x) or not (sphere.y) or not (sphere.z) or not (sphere.r) then 223 | return false; 224 | end 225 | 226 | return sphere; 227 | end 228 | 229 | local function readStringChunk(filePtr) 230 | local chunkHeader = rwReadChunkHeader(filePtr); 231 | 232 | if not (chunkHeader) then 233 | return false, "failed to read chunk header"; 234 | end 235 | 236 | if not (chunkHeader.type == 0x02) then 237 | return false, "not a string"; 238 | end 239 | 240 | local data = fileRead(filePtr, chunkHeader.size); 241 | 242 | if not (data) then 243 | return false, "failed to read string character bytes"; 244 | end 245 | 246 | -- Remove trailing bytes of zeroes. 247 | local non_zero_cnt = string.find(data, "\0"); 248 | 249 | if (non_zero_cnt) then 250 | return string.sub(data, 1, non_zero_cnt - 1); 251 | end 252 | 253 | return data; 254 | end 255 | 256 | function rwReadTextureInfo(filePtr) 257 | local chunkHeader = rwReadChunkHeader(filePtr); 258 | 259 | if not (chunkHeader) then 260 | return false, "failed to read chunk header"; 261 | end 262 | 263 | if not (chunkHeader.type == 0x06) then 264 | return false, "not a texture info"; 265 | end 266 | 267 | local structHeader = rwReadChunkHeader(filePtr); 268 | 269 | if not (structHeader) then 270 | return false, "failed to read struct chunk header"; 271 | end 272 | 273 | if not (structHeader.type == 1) then 274 | return false, "not a struct chunk"; 275 | end 276 | 277 | local tex_flags = _read_uint32(filePtr); 278 | 279 | if not (tex_flags) then 280 | return false, "failed to read texture info flags"; 281 | end 282 | 283 | local filterMode = bitExtract(tex_flags, 0, 8); 284 | local uAddr = bitExtract(tex_flags, 8, 4); 285 | local vAddr = bitExtract(tex_flags, 12, 4); 286 | local hasMips = bitExtract(tex_flags, 16, 1); 287 | 288 | local texName = readStringChunk(filePtr); 289 | 290 | if not (texName) then 291 | return false, "failed to read texture name"; 292 | end 293 | 294 | local maskName = readStringChunk(filePtr); 295 | 296 | if not (maskName) then 297 | return false, "failed to read mask name"; 298 | end 299 | 300 | local extension = readExtensions(filePtr); 301 | 302 | if not (extension) then 303 | return false, "failed to read extensions"; 304 | end 305 | 306 | local textureInfo = {}; 307 | textureInfo.filterMode = filterMode; 308 | textureInfo.uAddr = uAddr; 309 | textureInfo.vAddr = vAddr; 310 | textureInfo.hasMips = hasMips; 311 | textureInfo.texName = texName; 312 | textureInfo.maskName = maskName; 313 | textureInfo.extension = extension; 314 | 315 | return textureInfo; 316 | end 317 | 318 | function rwReadMaterial(filePtr) 319 | local chunkHeader = rwReadChunkHeader(filePtr); 320 | 321 | if not (chunkHeader) then 322 | return false, "failed to read chunk header"; 323 | end 324 | 325 | if not (chunkHeader.type == 0x07) then 326 | return false, "not a material"; 327 | end 328 | 329 | local structHeader = rwReadChunkHeader(filePtr); 330 | 331 | if not (structHeader) then 332 | return false, "failed to read struct chunk header"; 333 | end 334 | 335 | if not (structHeader.type == 1) then 336 | return false, "not a struct chunk"; 337 | end 338 | 339 | local mat_flags = _read_uint32(filePtr); 340 | local red = _read_byte(filePtr); 341 | local green = _read_byte(filePtr); 342 | local blue = _read_byte(filePtr); 343 | local alpha = _read_byte(filePtr); 344 | local pad0 = _read_uint32(filePtr); 345 | local int_isTextured = _read_uint32(filePtr); 346 | local ambient = _read_float32(filePtr); 347 | local specular = _read_float32(filePtr); 348 | local diffuse = _read_float32(filePtr); 349 | 350 | if not (mat_flags) or not (red) or not (green) or not (blue) or not (alpha) or 351 | not (pad0) or not (int_isTextured) or not (ambient) or not (specular) or 352 | not (diffuse) then 353 | 354 | return false, "failed to read struct data"; 355 | end 356 | 357 | local isTextured = not (int_isTextured == 0); 358 | 359 | local texture = false; 360 | 361 | if (isTextured) then 362 | local err; 363 | texture, err = rwReadTextureInfo(filePtr); 364 | 365 | if not (texture) then 366 | return false, "failed to read texture: " .. err; 367 | end 368 | end 369 | 370 | local extension, err = readExtensions(filePtr); 371 | 372 | if not (extension) then 373 | return false, "failed to read extensions: " .. err; 374 | end 375 | 376 | local material = {}; 377 | material.flags = mat_flags; 378 | material.red = red; 379 | material.green = green; 380 | material.blue = blue; 381 | material.alpha = alpha; 382 | material.texture = texture; 383 | material.ambient = ambient; 384 | material.specular = specular; 385 | material.diffuse = diffuse; 386 | material.extension = extension; 387 | 388 | return material; 389 | end 390 | 391 | local function readMaterialList(filePtr) 392 | local chunkHeader = rwReadChunkHeader(filePtr); 393 | 394 | if not (chunkHeader) then 395 | return false, "failed to read chunk header"; 396 | end 397 | 398 | if not (chunkHeader.type == 0x08) then 399 | return false, "not a material list"; 400 | end 401 | 402 | local structHeader = rwReadChunkHeader(filePtr); 403 | 404 | if not (structHeader) then 405 | return false, "failed to read struct header"; 406 | end 407 | 408 | if not (structHeader.type == 1) then 409 | return false, "not a struct header"; 410 | end 411 | 412 | local num_materials = _read_uint32(filePtr); 413 | 414 | if not (num_materials) then 415 | return false, "failed to read number of materials"; 416 | end 417 | 418 | local mat_indices = {}; 419 | 420 | for iter=1,num_materials,1 do 421 | local mat_idx = _read_uint32(filePtr); 422 | 423 | if not (mat_idx) then 424 | return false, "failed to read material index"; 425 | end 426 | 427 | mat_indices[iter] = mat_idx; 428 | end 429 | 430 | local materials = {}; 431 | 432 | for iter=1,num_materials,1 do 433 | local mat, err = rwReadMaterial(filePtr); 434 | 435 | if not (mat) then 436 | return false, "failed to read material #" .. iter ..": " .. err; 437 | end 438 | 439 | materials[iter] = mat; 440 | end 441 | 442 | local materialList = {}; 443 | materialList.list = materials; 444 | materialList.indices = mat_indices; 445 | 446 | return materialList; 447 | end 448 | 449 | local function rwReadGeometry(filePtr) 450 | local chunkHeader = rwReadChunkHeader(filePtr); 451 | 452 | if not (chunkHeader) then 453 | return false, "failed to read chunk header"; 454 | end 455 | 456 | if not (chunkHeader.type == 0x0F) then 457 | return false, "not a geometry"; 458 | end 459 | 460 | local structHeader = rwReadChunkHeader(filePtr); 461 | 462 | if not (structHeader) then 463 | return false, "failed to read struct chunk header"; 464 | end 465 | 466 | if not (structHeader.type == 1) then 467 | return false, "not a struct chunk"; 468 | end 469 | 470 | local formatFlags = _read_uint32(filePtr); 471 | local numTriangles = _read_uint32(filePtr); 472 | local numVertices = _read_uint32(filePtr); 473 | local numMorphTargets = _read_uint32(filePtr); 474 | 475 | if not (formatFlags) then 476 | return false, "failed to read format flags"; 477 | end 478 | 479 | if not (numTriangles) then 480 | return false, "failed to read num triangles"; 481 | end 482 | 483 | if not (numVertices) then 484 | return false, "failed to read num vertices"; 485 | end 486 | 487 | if not (numMorphTargets) then 488 | return false, "failed to read num morph targets"; 489 | end 490 | 491 | if (RW_DEBUG) then 492 | outputDebugString( "formatFlags: " .. formatFlags ); 493 | outputDebugString( "numTriangles: " .. numTriangles ); 494 | outputDebugString( "numVertices: " .. numVertices ); 495 | outputDebugString( "numMorphTargets: " .. numMorphTargets ); 496 | end 497 | 498 | -- What do we actually have? 499 | local is_tri_strip = bitTest(formatFlags, 0x00000001); 500 | local has_vertex_pos = bitTest(formatFlags, 0x00000002); 501 | local has_vertex_texcoord = bitTest(formatFlags, 0x00000004); 502 | local has_vertex_colors = bitTest(formatFlags, 0x00000008); 503 | local has_vertex_normals = bitTest(formatFlags, 0x00000010); 504 | local has_geom_lighting = bitTest(formatFlags, 0x00000020); 505 | local has_geom_mat_modulation = bitTest(formatFlags, 0x00000040); 506 | local has_geom_texcoord_2 = bitTest(formatFlags, 0x00000080); 507 | local is_geom_native = bitTest(formatFlags, 0x01000000); 508 | 509 | -- We ignore the flag bits that we do not care about 510 | -- This should not happen in clean implementations. 511 | 512 | -- I guess geometry can be missing from the container BUT DOES NOT HAVE TO. 513 | 514 | if (is_geom_native) then 515 | return false, "fatal: native geometry not supported"; 516 | end 517 | 518 | local arbitrary_numTexSets = bitExtract(formatFlags, 16, 8); 519 | 520 | local numTexSets = 0; 521 | 522 | if (arbitrary_numTexSets > 0) then 523 | numTexSets = arbitrary_numTexSets; 524 | else 525 | if (has_geom_texcoord) then 526 | numTexSets = 1; 527 | elseif (has_geom_texcoord_2) then 528 | numTexSets = 2; 529 | end 530 | end 531 | 532 | local commondata_vertices = {}; 533 | 534 | for iter=1,numVertices,1 do 535 | local vert = {}; 536 | vert.red = 0; 537 | vert.green = 0; 538 | vert.blue = 0; 539 | vert.alpha = 0; 540 | vert.texcoords = {}; 541 | commondata_vertices[iter] = vert; 542 | end 543 | 544 | if (has_vertex_colors) then 545 | for iter=1,numVertices,1 do 546 | local vert = commondata_vertices[iter]; 547 | vert.red = _read_byte(filePtr); 548 | vert.green = _read_byte(filePtr); 549 | vert.blue = _read_byte(filePtr); 550 | vert.alpha = _read_byte(filePtr); 551 | 552 | if not (vert.red) or not (vert.green) or not (vert.blue) or not (vert.alpha) then 553 | return false, "failed to read vertex color"; 554 | end 555 | end 556 | end 557 | 558 | for tsetn=1,numTexSets,1 do 559 | for iter=1,numVertices,1 do 560 | local vert = commondata_vertices[iter]; 561 | 562 | local texcoord = {}; 563 | texcoord.u = _read_float32(filePtr); 564 | texcoord.v = _read_float32(filePtr); 565 | 566 | if not (texcoord.u) or not (texcoord.v) then 567 | return false, "failed to read vertex texture coordinate #" .. tsetn; 568 | end 569 | 570 | vert.texcoords[tsetn] = texcoord; 571 | end 572 | end 573 | 574 | local triangles = {}; 575 | 576 | for iter=1,numTriangles,1 do 577 | local tri = {}; 578 | tri.vertex2 = _read_uint16(filePtr); 579 | tri.vertex1 = _read_uint16(filePtr); 580 | tri.mat_id = _read_uint16(filePtr); 581 | tri.vertex3 = _read_uint16(filePtr); 582 | 583 | if not (tri.vertex2) or not (tri.vertex1) or not (tri.mat_id) or not (tri.vertex3) then 584 | return false, "failed to read triangle"; 585 | end 586 | 587 | triangles[iter] = tri; 588 | end 589 | 590 | local morphTargets = {}; 591 | 592 | for mtiter=1,numMorphTargets,1 do 593 | local mtarget = {}; 594 | mtarget.sphere = readBoundingSphere(filePtr); 595 | local int_hasVertices = _read_uint32(filePtr); 596 | local int_hasNormals = _read_uint32(filePtr); 597 | 598 | if not (mtarget.sphere) or not (int_hasVertices) or not (int_hasNormals) then 599 | return false, "failed to read morph target #" .. mtiter; 600 | end 601 | 602 | local vertices = false; 603 | 604 | local hasVertices = not (int_hasVertices == 0); 605 | local hasNormals = not (int_hasNormals == 0); 606 | 607 | if (hasVertices) or (hasNormals) then 608 | vertices = {}; 609 | 610 | for iter=1,numVertices,1 do 611 | local vert = {}; 612 | vert.x = 0; 613 | vert.y = 0; 614 | vert.z = 0; 615 | vert.nx = 0; 616 | vert.ny = 0; 617 | vert.nz = 0; 618 | vertices[iter] = vert; 619 | end 620 | end 621 | 622 | if (hasVertices) then 623 | for iter=1,numVertices,1 do 624 | local vert = vertices[iter]; 625 | vert.x = _read_float32(filePtr); 626 | vert.y = _read_float32(filePtr); 627 | vert.z = _read_float32(filePtr); 628 | 629 | if not (vert.x) or not (vert.y) or not (vert.z) then 630 | return false, "failed to read morph target vertex translation"; 631 | end 632 | end 633 | end 634 | 635 | if (hasNormals) then 636 | for iter=1,numVertices,1 do 637 | local vert = vertices[iter]; 638 | vert.nx = _read_float32(filePtr); 639 | vert.ny = _read_float32(filePtr); 640 | vert.nz = _read_float32(filePtr); 641 | 642 | if not (vert.nx) or not (vert.ny) or not (vert.nz) then 643 | return false, "failed to read morph target vertex normal"; 644 | end 645 | end 646 | end 647 | 648 | mtarget.vertices = vertices; 649 | 650 | morphTargets[mtiter] = mtarget; 651 | end 652 | 653 | local materials, err = readMaterialList(filePtr); 654 | 655 | if not (materials) then 656 | return false, "failed to read material list: " .. err; 657 | end 658 | 659 | local extension, err = readExtensions(filePtr); 660 | 661 | if not (extension) then 662 | return false, "failed to read extensions: " .. err; 663 | end 664 | 665 | local geom = {}; 666 | geom.common_vertData = commondata_vertices; 667 | geom.triangles = triangles; 668 | geom.morphTargets = morphTargets; 669 | geom.hasDynamicLighting = has_geom_lighting; 670 | geom.hasMaterialModulation = has_geom_mat_modulation; 671 | geom.hasNativeData = is_geom_native; 672 | geom.materialList = materials; 673 | geom.extension = extension; 674 | 675 | return geom; 676 | end 677 | 678 | local function readGeometryList(filePtr) 679 | local chunkHeader = rwReadChunkHeader(filePtr); 680 | 681 | if not (chunkHeader) then 682 | return false, "failed to read chunk header"; 683 | end 684 | 685 | if not (chunkHeader.type == 0x1A) then 686 | return false, "not a geometry list"; 687 | end 688 | 689 | local structHeader = rwReadChunkHeader(filePtr); 690 | 691 | if not (structHeader) then 692 | return false, "failed to read struct chunk"; 693 | end 694 | 695 | if not (structHeader.type == 1) then 696 | return false, "not a struct chunk"; 697 | end 698 | 699 | local num_geoms = _read_uint32(filePtr); 700 | 701 | if not (num_geoms) then 702 | return false, "failed to read number of geometries"; 703 | end 704 | 705 | local geometries = {}; 706 | 707 | for iter=1,num_geoms,1 do 708 | local geom, err = rwReadGeometry(filePtr); 709 | 710 | if not (geom) then 711 | return false, "failed to read geometry #" .. iter .. ": " .. err; 712 | end 713 | 714 | geometries[iter] = geom; 715 | end 716 | 717 | return geometries; 718 | end 719 | 720 | function rwReadAtomic(filePtr) 721 | local chunkHeader = rwReadChunkHeader(filePtr); 722 | 723 | if not (chunkHeader) then 724 | return false, "failed to read chunk header"; 725 | end 726 | 727 | if not (chunkHeader.type == 0x14) then 728 | return false, "not an atomic"; 729 | end 730 | 731 | local structHeader = rwReadChunkHeader(filePtr); 732 | 733 | if not (structHeader) then 734 | return false, "failed to read struct chunk header"; 735 | end 736 | 737 | if not (structHeader.type == 1) then 738 | return false, "not a struct chunk"; 739 | end 740 | 741 | local frameIndex = _read_uint32(filePtr); 742 | local geomIndex = _read_uint32(filePtr); 743 | local flags = _read_uint32(filePtr); 744 | local unused = _read_uint32(filePtr); 745 | 746 | if not (frameIndex) or not (geomIndex) or not (flags) or not (unused) then 747 | return false, "failed to read struct members"; 748 | end 749 | 750 | local doCollisionTest = bitTest(flags, 0x01); 751 | local doRender = bitTest(flags, 0x04); 752 | 753 | local extension, err = readExtensions(filePtr); 754 | 755 | if not (extension) then 756 | return false, "failed to read extension: " .. err; 757 | end 758 | 759 | local atomic = {}; 760 | atomic.frameIndex = frameIndex; 761 | atomic.geomIndex = geomIndex; 762 | atomic.doCollisionText = doCollisionTest; 763 | atomic.doRender = doRender; 764 | atomic.extension = extension; 765 | 766 | return atomic; 767 | end 768 | 769 | function rwReadClump(filePtr) 770 | local mainChunkHeader = rwReadChunkHeader(filePtr); 771 | 772 | if not (mainChunkHeader) then 773 | fileClose(filePtr); 774 | return false, "failed to read DFF chunk header"; 775 | end 776 | 777 | if not (mainChunkHeader.version.libMajor == 3) or not (mainChunkHeader.version.relMajor >= 5) then 778 | return false, "only San Andreas files are supported (got " 779 | .. mainChunkHeader.version.libMajor .. "." .. mainChunkHeader.version.relMajor .. ")"; 780 | end 781 | 782 | if not (mainChunkHeader.type == 0x10) then 783 | return false, "not a DFF file (ID: " .. mainChunkHeader.type .. ")"; 784 | end 785 | 786 | -- Process clump stuff. 787 | local clumpMetaHeader = rwReadChunkHeader(filePtr); 788 | 789 | if not (clumpMetaHeader) then 790 | return false, "failed to read clump meta header"; 791 | end 792 | 793 | if not (clumpMetaHeader.type == 0x01) then 794 | return false, "not a struct chunk"; 795 | end 796 | 797 | local num_atomics = _read_uint32(filePtr); 798 | local num_lights = _read_uint32(filePtr); 799 | local num_cameras = _read_uint32(filePtr); 800 | 801 | if not (num_atomics) then 802 | return false, "failed to read number of atomics"; 803 | end 804 | 805 | if not (num_lights) then 806 | return false, "failed to read number of lights"; 807 | end 808 | 809 | if not (num_cameras) then 810 | return false, "failed to read number of cameras"; 811 | end 812 | 813 | if (num_lights > 0) then 814 | return false, "fatal: lights are not supported"; 815 | end 816 | 817 | if (num_cameras > 0) then 818 | return false, "fatal: cameras are not supported"; 819 | end 820 | 821 | local framelist, err = readFrameList(filePtr); 822 | 823 | if not (framelist) then 824 | return false, "failed to read framelist: " .. err; 825 | end 826 | 827 | local geomlist, err = readGeometryList(filePtr); 828 | 829 | if not (geomlist) then 830 | return false, "failed to read geometrylist: " .. err; 831 | end 832 | 833 | local atomics = {}; 834 | 835 | for iter=1,num_atomics,1 do 836 | local atomic, err = rwReadAtomic(filePtr); 837 | 838 | if not (atomic) then 839 | return false, "failed to read atomic: " .. err; 840 | end 841 | 842 | atomics[iter] = atomic; 843 | end 844 | 845 | local extension, err = readExtensions(filePtr); 846 | 847 | if not (extension) then 848 | return false, "failed to read extension: " .. err; 849 | end 850 | 851 | -- TODO: link up geometries with atomics and their frames. 852 | 853 | local dff = {}; 854 | dff.framelist = framelist; 855 | dff.geomlist = geomlist; 856 | dff.atomics = atomics; 857 | 858 | return dff; 859 | end -------------------------------------------------------------------------------- /shared.lua: -------------------------------------------------------------------------------- 1 | -- Optimizations. 2 | local error = error; 3 | local debug = debug; 4 | local outputDebugString = outputDebugString; 5 | 6 | function traceback() 7 | local level = debug.getinfo(1); 8 | local n = 1; 9 | 10 | while (level) do 11 | outputDebugString(level.short_src .. ": " .. level.currentline .. ", " .. level.linedefined); 12 | 13 | n = n + 1; 14 | level = debug.getinfo(n); 15 | end 16 | 17 | error("traceback!", 2); 18 | end -------------------------------------------------------------------------------- /single_test_draw.lua: -------------------------------------------------------------------------------- 1 | -- Possibly paste code from "debug_tri_draw.lua" here. 2 | -- PASTE BEGIN. 3 | local triangle = createPlane( 4 | createVector(0, 0, 5), 5 | createVector(25, 0, 0), 6 | createVector(0, 0, 15) 7 | ); 8 | local frustum = createViewFrustum( 9 | createVector(3, -26, 3), 10 | createVector(34, 559, 0), 11 | createVector(124, -7, 327), 12 | createVector(-746, 45, 285) 13 | ); 14 | local primType = "tri"; 15 | -- PASTE END. 16 | 17 | local function task_draw_test_scene(thread) 18 | local bbuf = create_backbuffer(640, 480, 255, 255, 0, 50); 19 | local dbuf = createDepthBuffer(640, 480, 1); 20 | 21 | local time_start = getTickCount(); 22 | 23 | do 24 | local gotToDraw, numDrawn, numSkipped, maxPixels = draw_plane_on_bbuf(frustum, bbuf, dbuf, triangle, true, primType, true); 25 | 26 | if ( gotToDraw ) then 27 | outputDebugString( "intersection successful (drawcnt: " .. numDrawn .. ", skipcnt: " .. numSkipped .. ", max: " .. maxPixels .. ")" ); 28 | else 29 | outputDebugString( "failed intersection" ); 30 | end 31 | end 32 | 33 | local time_end = getTickCount(); 34 | local ms_diff = ( time_end - time_start ); 35 | 36 | outputDebugString( "render time: " .. ms_diff .. "ms" ); 37 | 38 | taskUpdate( 1, "creating backbuffer color composition string" ); 39 | 40 | local bbuf_width_ushort = num_to_ushort_bytes( bbuf.width ); 41 | local bbuf_height_ushort = num_to_ushort_bytes( bbuf.height ); 42 | 43 | local pixels_str = table.concat(bbuf.items); 44 | 45 | local bbuf_string = 46 | pixels_str .. 47 | ( bbuf_width_ushort .. 48 | bbuf_height_ushort ); 49 | 50 | taskUpdate( false, "sending backbuffer to clients (render time: " .. ms_diff .. "ms)" ); 51 | 52 | local players = getElementsByType("player"); 53 | 54 | for m,n in ipairs(players) do 55 | triggerClientEvent(n, "onServerTransmitImage", root, bbuf_string); 56 | end 57 | 58 | outputDebugString("sent backbuffer to clients"); 59 | end 60 | 61 | addCommandHandler("debugdraw", 62 | function() 63 | spawnTask(task_draw_test_scene); 64 | end 65 | ); -------------------------------------------------------------------------------- /threads.lua: -------------------------------------------------------------------------------- 1 | local threads = createClass(); 2 | 3 | function createThread(start, userdata) 4 | local thread, env = createClass(); 5 | local startTime = 0; 6 | local susTime = 0; 7 | local corot = coroutine.create(start); 8 | 9 | function thread.sustime(ns) 10 | susTime = ns; 11 | return true; 12 | end 13 | 14 | function thread.userdata() 15 | return userdata; 16 | end 17 | 18 | function thread.resume() 19 | if (isrunning()) then return end; 20 | 21 | startTime = getTickCount(); 22 | 23 | coroutine.resume(corot, thread, userdata); 24 | 25 | if (coroutine.status(corot) == "dead") then 26 | destroy(); 27 | end 28 | end 29 | 30 | function thread.isrunning() 31 | return (coroutine.running() == corot); 32 | end 33 | 34 | function thread.yield() 35 | if not (isrunning()) then return false; end; 36 | 37 | if (getTickCount() - startTime < susTime) then return false; end; 38 | 39 | coroutine.yield(); 40 | 41 | return true; 42 | end 43 | 44 | function thread.destroy() 45 | corot = nil; 46 | end 47 | 48 | setfenv(start, env); 49 | 50 | thread.setParent(threads); 51 | return thread; 52 | end 53 | 54 | function threads_pulse() 55 | local m,n; 56 | 57 | threads.reference(); 58 | 59 | for m,n in ipairs(threads.children) do 60 | n.resume(); 61 | end 62 | 63 | threads.dereference(); 64 | return true; 65 | end 66 | 67 | createThread(function() 68 | while (true) do 69 | collectgarbage("step"); 70 | 71 | yield(); 72 | end 73 | end, 50, 0 74 | ).sustime(5); -------------------------------------------------------------------------------- /utilities_vendor.lua: -------------------------------------------------------------------------------- 1 | -- https://stackoverflow.com/questions/18886447/convert-signed-ieee-754-float-to-hexadecimal-representation 2 | -- lua-MessagePack 3 | function float2hex (n) 4 | if n == 0.0 then return 0.0 end 5 | 6 | local sign = 0 7 | if n < 0.0 then 8 | sign = 0x80 9 | n = -n 10 | end 11 | 12 | local mant, expo = math.frexp(n) 13 | local hext = {} 14 | 15 | if mant ~= mant then 16 | hext[#hext+1] = string.char(0xFF, 0x88, 0x00, 0x00) 17 | 18 | elseif mant == math.huge or expo > 0x80 then 19 | if sign == 0 then 20 | hext[#hext+1] = string.char(0x7F, 0x80, 0x00, 0x00) 21 | else 22 | hext[#hext+1] = string.char(0xFF, 0x80, 0x00, 0x00) 23 | end 24 | 25 | elseif (mant == 0.0 and expo == 0) or expo < -0x7E then 26 | hext[#hext+1] = string.char(sign, 0x00, 0x00, 0x00) 27 | 28 | else 29 | expo = expo + 0x7E 30 | mant = (mant * 2.0 - 1.0) * math.ldexp(0.5, 24) 31 | hext[#hext+1] = string.char(sign + math.floor(expo / 0x2), 32 | (expo % 0x2) * 0x80 + math.floor(mant / 0x10000), 33 | math.floor(mant / 0x100) % 0x100, 34 | mant % 0x100) 35 | end 36 | 37 | return tonumber(string.gsub(table.concat(hext),"(.)", 38 | function (c) return string.format("%02X%s",string.byte(c),"") end), 16) 39 | end 40 | 41 | local function bytevals2float(b1, b2, b3, b4) 42 | local sign = b1 > 0x7F 43 | local expo = (b1 % 0x80) * 0x2 + math.floor(b2 / 0x80) 44 | local mant = ((b2 % 0x80) * 0x100 + b3) * 0x100 + b4 45 | 46 | if sign then 47 | sign = -1 48 | else 49 | sign = 1 50 | end 51 | 52 | local n 53 | 54 | if mant == 0 and expo == 0 then 55 | n = sign * 0.0 56 | elseif expo == 0xFF then 57 | if mant == 0 then 58 | n = sign * math.huge 59 | else 60 | n = 0.0/0.0 61 | end 62 | else 63 | n = sign * math.ldexp(1.0 + mant / 0x800000, expo - 0x7F) 64 | end 65 | 66 | return n 67 | end 68 | 69 | function bytes2float (c) 70 | local b1,b2,b3,b4 = string.byte(c:reverse(), 1, 4) 71 | return bytevals2float(b1, b2, b3, b4); 72 | end 73 | 74 | -- UNIT TEST. 75 | assert( 1.0 == bytevals2float( 0x3F, 0x80, 0x00, 0x00 ) ); 76 | assert( 5.5 == bytevals2float( 0x40, 0xb0, 0x00, 0x00 ) ); --------------------------------------------------------------------------------