diff --git a/doc/gjk.py b/doc/gjk.py new file mode 100644 index 0000000000000000000000000000000000000000..b72b3e7d8ac7b2389032f6b460663df4aea35ebc --- /dev/null +++ b/doc/gjk.py @@ -0,0 +1,215 @@ +#!/usr/bin/env python3 +import pdb + +checks = [ "AB", "AC", "AD" ] \ + + [ "ABC", "ACD", "ADB" ] \ + + [ "ABC.cross({})".format(n) for n in [ "AB", "AC" ] ] \ + + [ "ACD.cross({})".format(n) for n in [ "AC", "AD" ] ] \ + + [ "ADB.cross({})".format(n) for n in [ "AD", "AB" ] ] + +tests = list(range(len(checks))) + +weights = [ 1, ] * 3 + [ 2, ] * 3 + [ 3, ] * 6 + +regions = [ "ABC", "ACD", "ADB", "AB", "AC", "AD", "A", "Inside", ] +cases = list(range(len(regions))) + +definitions = [ + [ 1, 4,- 5 ], + [ 2, 6,- 7 ], + [ 3, 8,- 9 ], + [- 4, 9, 10 ], + [- 6, 5, 11 ], + [- 8, 7, 12 ], + [-10,-11,-12 ], + [- 1,- 2,- 3 ], + ] +conditions = [ + [], + [], + [], + [], + [], + [], + [], + [ [10, 11, 12], ], + ] +rejections = [ + [], + [], + [], + [ [- 1,- 3], ], + [ [- 2,- 1], ], + [ [- 3,- 2], ], + [ [ 4,- 5], [ 6,- 7], [ 8,- 9], ], + [], + ] + +def apply_test_values (cases, test_values): + def canSatisfy (values, indices): + for k in indices: + if k > 0 and values[ k-1] == False: return False + if k < 0 and values[-k-1] == True : return False + return True + def satisfies (values, indices): + for k in indices: + if k > 0 and values[ k-1] != True : return False + if k < 0 and values[-k-1] != False: return False + return True + + # Check whether all cases are + left_cases = [] + for case in cases: + defi = definitions[case] + conds = conditions[case] + rejs = rejections[case] + if satisfies (test_values, defi): + # A definition is True, stop recursion + return [ case ] + if not canSatisfy (test_values, defi): + continue + for cond in conds: + if satisfies (test_values, cond): + # A condition is True, stop recursion + return [ case ] + append = True + for rej in rejs: + if satisfies (test_values, rej): + # A rejection is True, discard this case + append = False + break + if append: left_cases.append (case) + return left_cases + +def max_number_of_tests (current_tests, cases, test_values = [None,]*len(tests), prevBestScore = float('inf'), prevScore = 0): + for test in current_tests: + assert test_values[test] == None, "Test " +str(test)+ " already performed" + + left_cases = apply_test_values (cases, test_values) + + if len(left_cases) == 1: + return prevScore, { 'case': left_cases[0], } + elif len(left_cases) == 0: + return prevScore, { } + + assert len(current_tests) > 0, "No more test but " + str(left_cases) + " remains" + + remaining_tests = current_tests[1:] + currentBestScore = prevBestScore + bestScore = float('inf') + bestOrder = [None, None] + for i, test in enumerate(current_tests): + assert bestScore >= currentBestScore + + if i > 0: + remaining_tests[i-1] = current_tests[i-1] + + currentScore = prevScore + len(left_cases) * weights[test] + if currentScore > currentBestScore: # Cannot do better -> stop + continue + + test_values[test] = True + score_if_t, order_if_t = max_number_of_tests (remaining_tests, left_cases, test_values, currentBestScore, currentScore) + if score_if_t >= currentBestScore: # True didn't do better -> stop + test_values[test] = None + continue + + test_values[test] = False + score_if_f, order_if_f = max_number_of_tests (remaining_tests, left_cases, test_values, currentBestScore, currentScore) + test_values[test] = None + + currentScore = max(score_if_t, score_if_f) + if currentScore < bestScore: + if currentScore < currentBestScore: + bestScore = currentScore + bestOrder = { 'test': test, 'true': order_if_t, 'false': order_if_f } + #pdb.set_trace() + currentBestScore = currentScore + if len(tests) == len(current_tests): + print ("New best score: {}".format(currentBestScore)) + + return bestScore, bestOrder + +def printOrder (order, indent = "", start=True): + if start: + print (indent+"// The code of this function was generated using doc/gjk.py") + print (indent+"const int a = 3, b = 2, c = 1, d = 0;") + for l in "abcd": + print (indent+"const Vec3f& {} (current.vertex[{}]->w);".format(l.upper(),l)) + for l in "BCD": + print (indent+"const Vec3f A{0} ({0}-A);".format(l)) + print (indent+"const FCL_REAL A{0}_dot_AO = A{0}.dot(-A);".format(l)) + for l1,l2 in zip("BCD","CDB"): + print (indent+"const Vec3f A{0}{1} (A{0}.cross(A{1}));".format(l1,l2)) + print (indent+"const FCL_REAL A{0}{1}_dot_AO = A{0}{1}.dot(-A);".format(l1,l2)) + print (indent+"Vec3f cross;") + print() + print( "#define REGION_INSIDE() "+indent+"\\") + print(indent+" ray.setZero(); \\") + print(indent+" next.vertex[0] = current.vertex[d]; \\") + print(indent+" next.vertex[1] = current.vertex[c]; \\") + print(indent+" next.vertex[2] = current.vertex[b]; \\") + print(indent+" next.vertex[3] = current.vertex[a]; \\") + print(indent+" next.rank=4; \\") + print(indent+" return 0;") + print() + + if 'case' in order: + region = regions[order['case']] + print (indent + "// Region " + region) + toFree = ['a', 'b', 'c', 'd'] + if region == "Inside": + print (indent + "REGION_INSIDE()") + toFree = [] + elif region == 'A': + print (indent + "originToPoint (current, a, A, next, ray);") + toFree.remove('a') + elif len(region)==2: + a = region[0] + b = region[1] + print (indent + "originToSegment (current, {}, {}, {}, {}, {}, {}_dot_AO, next, ray);".format( + a.lower(), b.lower(), a, b, region, region)) + toFree.remove(a.lower()) + toFree.remove(b.lower()) + elif len(region)==3: + a = region[0] + b = region[1] + c = region[2] + print (indent + "originToTriangle (current, {}, {}, {}, {}, {}_dot_AO next, ray);".format( + a.lower(), b.lower(), c.lower(), region, region)) + toFree.remove(a.lower()) + toFree.remove(b.lower()) + toFree.remove(c.lower()) + else: + assert False, "Unknown region " + region + for pt in toFree: + print (indent + "free_v[nfree++] = current.vertex[{}];".format(pt)) + else: + assert "test" in order and 'true' in order and 'false' in order + check = checks[order['test']] + if 'cross' in check: + print (indent + "cross.noalias() = {};".format(checks[order['test']])) + print (indent + "if (cross.dot(-A) >= 0) {") + elif len(check) == 2 or len(check) == 3: + print (indent + "if ({}_dot_AO >= 0) {{".format(checks[order['test']])) + else: + assert False, "Unknown check " + check + #print (indent + "if ({}.dot(-A) >= 0) {{".format(checks[order['test']])) + printOrder (order['true'], indent=indent+" ", start=False) + print (indent + "} else {") + printOrder (order['false'], indent=indent+" ", start=False) + print (indent + "}") + + if start: + print() + print("#undef REGION_INSIDE") + print(indent+"return ray.squaredNorm();") + +score, order = max_number_of_tests (tests, cases) + +print(score) +printOrder(order, indent=" ") + +# TODO add weights such that: +# - it is preferred to have all the use of one check in one branch. +# idea: ponderate by the number of remaining tests.