├── .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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
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 ) );
--------------------------------------------------------------------------------