// SPDX-License-Identifier: Apache-2.0 // difftest shim for unit `geom.tree`. Wraps upstream SmTree / SmTreeNode / SmObjectList // (source/SMLib/src/SmTree.cpp, source/SMLib/inc/SmTree.h). // // Two program interpreters; the program encodings are shared with // crates/solidcore-ffi/src/units/geom_tree.rs (which validates them first). // // Spatial trees: sc_geom_tree_spatial_{ints,floats}(prog, n, out, cap, out_len) run the // same flat double program and return its integer or float output stream. // 0 e6 SmTree(extent) (must be first, or op 1) // 1 p3 size lst SmTree(point, size, NULL, lst ? &list : NULL) // 2 e6 obj geom AddToSpatialTree ints: status // 3 e6 obj RmFromSpatialTree ints: result // 4 e6 FindNode ints: node index or -1 // 5 e6 GetObjectsInBox ints: status, n, objs // 6 e6 IntersectsBox ints: result // 7 dump ints: top, n_nodes, per node parent c1 c2 // aux geom n_items items..., then // GetObjectList: status, n, objs; // floats: node boxes, item boxes, list boxes // 8 node SmTreeNode::GetObjectList ints: status, n, objs // 9 type/owner queries ints: GetType, IsKindOf x3, owner null, // owner object null, node owner object null // Objects are fake SmObject pointers g_objs + id (never dereferenced on these paths); // obj -1 is NULL. Nodes are reported by their index in GetAllTreeNodes (allocation order). // // Subdivision trees: sc_geom_tree_subdiv(nodes, n_nodes, prog, n_prog, out, cap, out_len) // builds n_nodes SmTreeNodes (node 0 = top) of an SmTree(NULL), each from 9 int64s: // parent c1 c2 aux_type split_dir marked node_class face surface (-1 = NULL), every node // with its own SmBezierAux2d. Program (int64): // 0 node neighbours: Left, Right, Bot, Top, GetNeighbors (each n, indices) // 1 node PropagateToParents // 2 node marked class face set aux fields // 3 dump marks: per node marked, class, face // 4 node GetFirstBezierPatch: 0 = NULL, 1 = non-NULL; -2 = not run (path needs // SmBezierPatch / subdivision surfaces this harness does not build) // 5 node SmTreeNode::GetOwnerObject: 0 = NULL #include "sc_shim.h" #include #include #include #include #include #include namespace { const int64_t kMaxObjects = 4096; alignas(16) char g_objs[kMaxObjects]; SmObject* obj_ptr(double id) { long long i = (long long)id; return i < 0 ? nullptr : reinterpret_cast(g_objs + i); } int64_t obj_id(const void* p) { return p == nullptr ? -1 : (int64_t)(static_cast(p) - g_objs); } SmExtent3d ext(const double* d) { return SmExtent3d(d[0], d[1], d[2], d[3], d[4], d[5]); } void put_ext(std::vector& o, const SmExtent3d& e) { o.push_back(e.GetMin().x); o.push_back(e.GetMin().y); o.push_back(e.GetMin().z); o.push_back(e.GetMax().x); o.push_back(e.GetMax().y); o.push_back(e.GetMax().z); } int64_t st(SmStatus s) { return s == SM_SUCCESS ? 0 : (int64_t)s; } int64_t op_size(int op) { switch (op) { case 0: return 6; case 1: return 5; case 2: return 8; case 3: return 7; case 4: return 6; case 5: return 6; case 6: return 6; case 7: return 0; case 8: return 1; case 9: return 0; default: return -1; } } struct Index { SmTArray nodes; std::map idx; explicit Index(const SmTree& t) { t.GetAllTreeNodes(nodes); for (ULONG i = 0; i < nodes.GetSize(); i++) idx[nodes[i]] = (int64_t)i; } int64_t of(const SmTreeNode* n) const { if (n == nullptr) return -1; auto it = idx.find(n); return it == idx.end() ? -2 : it->second; } }; // Runs the ops of a spatial program after its constructor (at p[i]). int32_t run_ops(SmTree* tree, bool point, const double* p, int64_t n, int64_t i, std::vector& oi, std::vector& of) { while (i < n) { int op = (int)p[i]; int64_t sz = op_size(op); if (sz < 0 || i + 1 + sz > n) return SC_ERR_ARG; const double* a = p + i + 1; if (op <= 1) return SC_ERR_ARG; // exactly one constructor, first switch (op) { case 2: { if (point) return SC_ERR_ARG; // point trees have no object memory manager int g = (int)a[7]; if (g < 0 || g > 3) return SC_ERR_ARG; oi.push_back(st(tree->AddToSpatialTree(ext(a), obj_ptr(a[6]), (SmNodeGeomType)g))); break; } case 3: oi.push_back(tree->RmFromSpatialTree(ext(a), obj_ptr(a[6])) ? 1 : 0); break; case 4: { Index ix(*tree); oi.push_back(ix.of(tree->FindNode(ext(a)))); break; } case 5: { SmTArray objs; oi.push_back(st(tree->GetObjectsInBox(ext(a), objs))); oi.push_back((int64_t)objs.GetSize()); for (ULONG k = 0; k < objs.GetSize(); k++) oi.push_back(obj_id(objs[k])); break; } case 6: oi.push_back(tree->IntersectsBox(ext(a)) ? 1 : 0); break; case 7: { Index ix(*tree); oi.push_back(ix.of(tree->GetTopNode())); oi.push_back((int64_t)ix.nodes.GetSize()); for (ULONG k = 0; k < ix.nodes.GetSize(); k++) { const SmTreeNode* nd = ix.nodes[k]; oi.push_back(ix.of(nd->m_pParent)); oi.push_back(ix.of(nd->m_pChild1)); oi.push_back(ix.of(nd->m_pChild2)); oi.push_back((int64_t)nd->m_eAuxDataType); oi.push_back((int64_t)nd->m_eGeomType); put_ext(of, ((SmTreeNode*)nd)->GetBoundingBox()); SmTArray items; if (nd->m_eAuxDataType == SM_AD_OBJECT_LIST) nd->GetObjectList(items); oi.push_back((int64_t)items.GetSize()); for (ULONG m = 0; m < items.GetSize(); m++) { oi.push_back(obj_id(items[m]->m_pObject)); put_ext(of, items[m]->m_sBBox); } } SmTArray all; oi.push_back(st(tree->GetObjectList(all))); oi.push_back((int64_t)all.GetSize()); for (ULONG m = 0; m < all.GetSize(); m++) { oi.push_back(obj_id(all[m]->m_pObject)); put_ext(of, all[m]->m_sBBox); } break; } case 8: { Index ix(*tree); long long k = (long long)a[0]; if (k < 0 || k >= (long long)ix.nodes.GetSize()) { oi.push_back(-3); break; } SmTArray items; SmStatus s = ix.nodes[(ULONG)k]->GetObjectList(items); oi.push_back(st(s)); oi.push_back((int64_t)items.GetSize()); for (ULONG m = 0; m < items.GetSize(); m++) oi.push_back(obj_id(items[m]->m_pObject)); break; } case 9: { oi.push_back((int64_t)tree->GetType()); oi.push_back(tree->IsKindOf(SmTree_TYPE) ? 1 : 0); oi.push_back(tree->IsKindOf(SmObject_TYPE) ? 1 : 0); oi.push_back(tree->IsKindOf(SmTree_TYPE + 1) ? 1 : 0); oi.push_back(tree->GetOwner() == nullptr ? 1 : 0); oi.push_back(tree->GetOwnerObject() == nullptr ? 1 : 0); oi.push_back(tree->GetTopNode()->GetOwnerObject() == nullptr ? 1 : 0); break; } default: return SC_ERR_ARG; } i += 1 + sz; } return SC_OK; } // Runs a spatial program; returns SC_OK or SC_ERR_ARG. int32_t run_spatial(const double* p, int64_t n, std::vector& oi, std::vector& of) { if (n < 1) return SC_ERR_ARG; int op = (int)p[0]; if (op == 0) { if (n < 7) return SC_ERR_ARG; SmTree tree(ext(p + 1)); return run_ops(&tree, false, p, n, 7, oi, of); } if (op == 1) { if (n < 6) return SC_ERR_ARG; SmObjectList list; // the point tree's caller-owned object list SmTree tree(SmPoint3d(p[1], p[2], p[3]), p[4], nullptr, p[5] != 0.0 ? &list : nullptr); return run_ops(&tree, true, p, n, 6, oi, of); } return SC_ERR_ARG; } void put_list(std::vector& o, const SmTArray& l, const std::vector& nodes) { o.push_back((int64_t)l.GetSize()); for (ULONG k = 0; k < l.GetSize(); k++) o.push_back((int64_t)(l[k] - nodes.data())); } } // namespace SC_EXPORT int32_t sc_geom_tree_spatial_ints(const double* prog, int64_t n, int64_t* out, int64_t cap, int64_t* out_len) { SC_TRY std::vector oi; std::vector of; int32_t s = run_spatial(prog, n, oi, of); if (s != SC_OK) return s; return sc_copy_out(oi.data(), (int64_t)oi.size(), out, cap, out_len); SC_CATCH } SC_EXPORT int32_t sc_geom_tree_spatial_floats(const double* prog, int64_t n, double* out, int64_t cap, int64_t* out_len) { SC_TRY std::vector oi; std::vector of; int32_t s = run_spatial(prog, n, oi, of); if (s != SC_OK) return s; return sc_copy_out(of.data(), (int64_t)of.size(), out, cap, out_len); SC_CATCH } SC_EXPORT int32_t sc_geom_tree_subdiv(const int64_t* desc, int64_t n_nodes, const int64_t* prog, int64_t n_prog, int64_t* out, int64_t cap, int64_t* out_len) { if (n_nodes < 1 || n_prog < 0) return SC_ERR_ARG; for (int64_t k = 0; k < n_nodes; k++) { const int64_t* d = desc + 9 * k; for (int j = 0; j < 3; j++) if (d[j] < -1 || d[j] >= n_nodes) return SC_ERR_ARG; if (d[3] < 0 || d[3] > 8 || d[4] < 0 || d[4] > 2 || d[6] < 0 || d[6] > 3) return SC_ERR_ARG; if (d[7] < -1 || d[7] >= kMaxObjects || d[8] < -1 || d[8] >= kMaxObjects) return SC_ERR_ARG; } SC_TRY SmTree tree((SmObject*)nullptr); std::vector nodes((size_t)n_nodes); std::vector aux((size_t)n_nodes); auto ptr = [&](int64_t k) -> SmTreeNode* { return k < 0 ? nullptr : &nodes[(size_t)k]; }; for (int64_t k = 0; k < n_nodes; k++) { const int64_t* d = desc + 9 * k; SmTreeNode& nd = nodes[(size_t)k]; nd.m_pTree = &tree; nd.m_pParent = ptr(d[0]); nd.m_pChild1 = ptr(d[1]); nd.m_pChild2 = ptr(d[2]); nd.m_eAuxDataType = (SmAuxDataType)d[3]; SmBezierAux2d& a = aux[(size_t)k]; a.m_eSplitDir = (SmSurfParamType)d[4]; a.m_bIsMarked = d[5] != 0 ? TRUE : FALSE; a.m_eNodeClass = (SmNodeClassType)d[6]; a.m_pFace = obj_ptr((double)d[7]); a.mBA_pSurface = (const SmSurface*)obj_ptr((double)d[8]); nd.m_pData = &a; } std::vector o; int64_t i = 0; while (i < n_prog) { int64_t op = prog[i]; int64_t sz = op == 0 || op == 1 || op == 4 || op == 5 ? 1 : op == 2 ? 4 : op == 3 ? 0 : -1; if (sz < 0 || i + 1 + sz > n_prog) return SC_ERR_ARG; const int64_t* a = prog + i + 1; if (sz >= 1 && (a[0] < 0 || a[0] >= n_nodes)) return SC_ERR_ARG; switch (op) { case 0: { const SmTreeNode& nd = nodes[(size_t)a[0]]; SmTArray l; nd.GetLeftNeighbors(l); put_list(o, l, nodes); l.ReSet(); nd.GetRightNeighbors(l); put_list(o, l, nodes); l.ReSet(); nd.GetBotNeighbors(l); put_list(o, l, nodes); l.ReSet(); nd.GetTopNeighbors(l); put_list(o, l, nodes); l.ReSet(); nd.GetNeighbors(l); put_list(o, l, nodes); break; } case 1: nodes[(size_t)a[0]].PropagateToParents(); break; case 2: { if (a[2] < 0 || a[2] > 3 || a[3] < -1 || a[3] >= kMaxObjects) return SC_ERR_ARG; SmBezierAux2d& x = aux[(size_t)a[0]]; x.m_bIsMarked = a[1] != 0 ? TRUE : FALSE; x.m_eNodeClass = (SmNodeClassType)a[2]; x.m_pFace = obj_ptr((double)a[3]); break; } case 3: for (int64_t k = 0; k < n_nodes; k++) { o.push_back(aux[(size_t)k].m_bIsMarked ? 1 : 0); o.push_back((int64_t)aux[(size_t)k].m_eNodeClass); o.push_back(obj_id(aux[(size_t)k].m_pFace)); } break; case 4: { // Walk the upstream decision path without touching the parts this harness // does not build (SmBezierPatch, subdivision surfaces); with a NULL owner the // remaining paths return NULL. const SmTreeNode& nd = nodes[(size_t)a[0]]; SmAuxDataType t = nd.m_eAuxDataType; bool skip = t == SM_AD_BEZIER_SURFACE || ((t == SM_AD_BEZIER_SURFACE_HEAD || t == SM_AD_AUX_DATA) && ((const SmBezierAux2d*)nd.m_pData)->mBA_pSurface != nullptr); o.push_back(skip ? -2 : (nodes[(size_t)a[0]].GetFirstBezierPatch() == nullptr ? 0 : 1)); break; } case 5: o.push_back(nodes[(size_t)a[0]].GetOwnerObject() == nullptr ? 0 : 1); break; default: return SC_ERR_ARG; } i += 1 + sz; } return sc_copy_out(o.data(), (int64_t)o.size(), out, cap, out_len); SC_CATCH }