mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-08 13:01:52 +00:00
e8202ea698
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
395 lines
14 KiB
C++
395 lines
14 KiB
C++
// 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 <SmExtent3d.h>
|
|
#include <SmSurfaceCache.h>
|
|
#include <SmTArray.h>
|
|
#include <SmTree.h>
|
|
|
|
#include <map>
|
|
#include <vector>
|
|
|
|
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<SmObject*>(g_objs + i);
|
|
}
|
|
|
|
int64_t obj_id(const void* p)
|
|
{
|
|
return p == nullptr ? -1 : (int64_t)(static_cast<const char*>(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<double>& 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<SmTreeNode*> nodes;
|
|
std::map<const SmTreeNode*, int64_t> 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<int64_t>& oi,
|
|
std::vector<double>& 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<SmObject*> 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<SmObjectList*> 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<SmObjectList*> 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<SmObjectList*> 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<int64_t>& oi, std::vector<double>& 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<int64_t>& o, const SmTArray<SmTreeNode*>& l, const std::vector<SmTreeNode>& 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<int64_t> oi;
|
|
std::vector<double> 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<int64_t> oi;
|
|
std::vector<double> 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<SmTreeNode> nodes((size_t)n_nodes);
|
|
std::vector<SmBezierAux2d> 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<int64_t> 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<SmTreeNode*> 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
|
|
}
|