@kitware/vtk.js
Version:
Visualization Toolkit for the Web
748 lines (701 loc) • 25.2 kB
JavaScript
import { m as macro } from '../../macros2.js';
import vtkCellArray from '../Core/CellArray.js';
import { b as vtkMath } from '../Core/Math/index.js';
import vtkPoints from '../Core/Points.js';
import vtkAbstractPointLocator from './AbstractPointLocator.js';
import vtkBoundingBox from './BoundingBox.js';
const {
vtkErrorMacro
} = macro;
// ----------------------------------------------------------------------------
// vtkPointLocator methods
// ----------------------------------------------------------------------------
function vtkPointLocator(publicAPI, model) {
// Set our className
model.classHierarchy.push('vtkPointLocator');
/**
* Calculate the squared distance from point x to the bucket "nei".
* @param {Vector3} x The point coordinates
* @param {Vector3} nei The bucket coordinates
* @returns {Number} The squared distance to the bucket
*/
function distance2ToBucket(x, nei) {
// Compute bucket bounds
const bounds = [nei[0] * model.HX + model.BX, (nei[0] + 1) * model.HX + model.BX, nei[1] * model.HY + model.BY, (nei[1] + 1) * model.HY + model.BY, nei[2] * model.HZ + model.BZ, (nei[2] + 1) * model.HZ + model.BZ];
return vtkBoundingBox.distance2ToBounds(x, bounds);
}
/**
* Get the neighboring buckets for a given bucket index.
* @param {Number[]} ijk The bucket index
* @param {Number[]} ndivs The number of divisions in each dimension
* @param {Number} level The level of neighbors to retrieve
* @returns An array of neighboring bucket indices
*/
function getBucketNeighbors(ijk, ndivs, level) {
const buckets = [];
if (level === 0) {
buckets.push([...ijk]);
return buckets;
}
const minLevel = [];
const maxLevel = [];
for (let i = 0; i < 3; i++) {
const min = ijk[i] - level;
const max = ijk[i] + level;
minLevel[i] = min > 0 ? min : 0;
maxLevel[i] = max < ndivs[i] - 1 ? max : ndivs[i] - 1;
}
for (let i = minLevel[0]; i <= maxLevel[0]; i++) {
for (let j = minLevel[1]; j <= maxLevel[1]; j++) {
for (let k = minLevel[2]; k <= maxLevel[2]; k++) {
if (i === ijk[0] + level || i === ijk[0] - level || j === ijk[1] + level || j === ijk[1] - level || k === ijk[2] + level || k === ijk[2] - level) {
buckets.push([i, j, k]);
}
}
}
}
return buckets;
}
/**
* Calculate the overlapping buckets for a given point and distance.
* @param {Vector3} x The point coordinates
* @param {*} ijk The bucket index
* @param {*} dist The search distance
* @param {*} level The level of detail
* @returns An array of overlapping bucket indices
*/
function getOverlappingBuckets(x, ijk, dist, level) {
const buckets = [];
const xBounds = [x[0], x[0], x[1], x[1], x[2], x[2]];
const bbox = vtkBoundingBox.newInstance();
bbox.setBounds(xBounds);
bbox.inflate(dist);
const ijkBounds = [ijk[0], ijk[0], ijk[1], ijk[1], ijk[2], ijk[2]];
const ijkBox = vtkBoundingBox.newInstance();
ijkBox.setBounds(ijkBounds);
ijkBox.inflate(level);
const minLevel = publicAPI.getBucketIndices([bbox.getBounds()[0], bbox.getBounds()[2], bbox.getBounds()[4]]);
const maxLevel = publicAPI.getBucketIndices([bbox.getBounds()[1], bbox.getBounds()[3], bbox.getBounds()[5]]);
// Iterate through potential buckets
for (let i = minLevel[0]; i <= maxLevel[0]; i++) {
for (let j = minLevel[1]; j <= maxLevel[1]; j++) {
for (let k = minLevel[2]; k <= maxLevel[2]; k++) {
if (!ijkBox.containsPoint(i, j, k)) {
buckets.push([i, j, k]);
}
}
}
}
return buckets;
}
/**
* Get the bucket index for a given bucket coordinate.
* @param {Number[]} ijk The bucket index
* @returns The bucket index
*/
function getBucketIndex(ijk) {
return ijk[0] + ijk[1] * model.XD + ijk[2] * model.sliceSize;
}
/**
* Get the bucket indices for a given point.
* @param {Vector3} point The point coordinates
* @returns The bucket indices
*/
publicAPI.getBucketIndices = point => {
const ix = Math.floor((point[0] - model.BX) * model.FX);
const iy = Math.floor((point[1] - model.BY) * model.FY);
const iz = Math.floor((point[2] - model.BZ) * model.FZ);
const ijk = [];
ijk[0] = Math.max(0, Math.min(ix, model.XD - 1));
ijk[1] = Math.max(0, Math.min(iy, model.YD - 1));
ijk[2] = Math.max(0, Math.min(iz, model.ZD - 1));
return ijk;
};
/**
* Get the bucket index for a given point.
* @param {Vector3} point The point coordinates
* @returns The bucket index
*/
publicAPI.getBucketIndex = point => {
const ijk = publicAPI.getBucketIndices(point);
return getBucketIndex(ijk);
};
/**
* Build the locator from the input dataset.
*/
publicAPI.buildLocator = () => {
model.level = 1;
const bounds = model.dataSet.getBounds();
const numPts = model.dataSet.getNumberOfPoints();
let numBuckets = Math.ceil(numPts / model.numberOfPointsPerBucket);
const ndivs = [0, 0, 0];
const bbox = vtkBoundingBox.newInstance();
bbox.setBounds(bounds);
if (model.automatic) {
bbox.computeDivisions(numBuckets, ndivs, model.bounds);
} else {
model.bounds = bbox.inflate();
for (let i = 0; i < 3; i++) {
ndivs[i] = Math.max(1, model.divisions[i]);
}
}
model.divisions = ndivs;
numBuckets = ndivs[0] * ndivs[1] * ndivs[2];
model.numberOfBuckets = numBuckets;
// Compute width of bucket in three directions
for (let i = 0; i < 3; ++i) {
model.H[i] = (model.bounds[2 * i + 1] - model.bounds[2 * i]) / ndivs[i];
}
model.hashTable.clear();
publicAPI.computePerformanceFactors();
for (let i = 0; i < numPts; ++i) {
const pt = model.dataSet.getPoints().getPoint(i);
const key = publicAPI.getBucketIndex(pt);
if (!model.hashTable.has(key)) {
model.hashTable.set(key, []); // Initialize bucket if it doesn't exist
}
const bucket = model.hashTable.get(key);
bucket.push(i);
}
};
publicAPI.initialize = () => {
model.points = null;
publicAPI.freeSearchStructure();
};
/**
* Initialize point insertion.
* @param {*} points The points to insert
* @param {Bounds} bounds The bounds for the points
* @param {Number} estNumPts Estimated number of points for insertion
*/
publicAPI.initPointInsertion = (points, bounds, estNumPts = 0) => {
if (points == null) {
vtkErrorMacro('A valid vtkPoints object is required for point insertion');
return false;
}
if (!bounds || bounds.length !== 6) {
vtkErrorMacro('A valid bounds array of length 6 is required');
return false;
}
if (!points) {
vtkErrorMacro('A valid vtkPoints is required for point insertion');
return false;
}
publicAPI.freeSearchStructure();
model.insertionPointId = 0;
model.points = points;
model.points.setNumberOfComponents(3);
model.points.initialize();
let numBuckets = 0;
const ndivs = [0, 0, 0];
const bbox = vtkBoundingBox.newInstance();
bbox.setBounds(bounds);
if (model.automatic && estNumPts > 0) {
numBuckets = Math.ceil(estNumPts / model.numberOfPointsPerBucket);
bbox.computeDivisions(numBuckets, ndivs, model.bounds);
} else {
model.bounds = bbox.inflate();
for (let i = 0; i < 3; i++) {
ndivs[i] = Math.max(1, model.divisions[i]);
}
}
model.divisions = ndivs;
numBuckets = ndivs[0] * ndivs[1] * ndivs[2];
model.numberOfBuckets = numBuckets;
// Compute width of bucket in three directions
for (let i = 0; i < 3; ++i) {
model.H[i] = (model.bounds[2 * i + 1] - model.bounds[2 * i]) / ndivs[i];
}
model.insertionTol2 = model.tolerance * model.tolerance;
let maxDivs = 0;
let hmin = Number.MAX_VALUE;
for (let i = 0; i < 3; i++) {
hmin = model.H[i] < hmin ? model.H[i] : hmin;
maxDivs = maxDivs > model.divisions[i] ? maxDivs : model.divisions[i];
}
model.insertionLevel = Math.ceil(model.tolerance / hmin);
model.insertionLevel = model.insertionLevel > maxDivs ? maxDivs : model.insertionLevel;
publicAPI.computePerformanceFactors();
return true;
};
/**
* Insert a point into the point locator.
* If the point is already present, it returns the existing ID.
* Otherwise, it inserts the point and returns a new ID.
*
* @param {Number} ptId The index of the point to insert.
* @param {Vector3} x The point to insert.
* @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
*/
publicAPI.insertPoint = (ptId, x) => {
const key = publicAPI.getBucketIndex(x);
if (!model.hashTable.has(key)) {
model.hashTable.set(key, []);
}
const bucket = model.hashTable.get(key);
bucket.push(ptId);
model.points.insertPoint(ptId, x);
return {
inserted: true,
id: ptId
};
};
/**
* Insert a point into the point locator.
* If the point is already present, it returns the existing ID.
* Otherwise, it inserts the point and returns a new ID.
*
* @param {Vector3} x The point to insert.
* @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
*/
publicAPI.insertNextPoint = x => {
const key = publicAPI.getBucketIndex(x);
if (!model.hashTable.has(key)) {
model.hashTable.set(key, []);
}
const bucket = model.hashTable.get(key);
bucket.push(model.insertionPointId);
model.points.insertPoint(model.insertionPointId, x);
return {
inserted: true,
id: model.insertionPointId++
};
};
/**
* Insert a point into the point locator.
* If the point is already present, it returns the existing ID.
* Otherwise, it inserts the point and returns a new ID.
*
* @param {Vector3} x The point to insert.
* @returns {IInsertPointResult} An object indicating if the point was inserted and its ID.
*/
publicAPI.insertUniquePoint = x => {
const ptId = publicAPI.isInsertedPoint(x);
if (ptId > -1) {
// Point already exists
return {
inserted: false,
id: ptId
};
}
// Insert new point
const ret = publicAPI.insertNextPoint(x);
return ret;
};
/**
* Check if a point is already inserted in the point locator.
*
* @param {Vector3} x The point to check.
* @returns {Number} The ID of the point if it exists, otherwise -1.
*/
publicAPI.isInsertedPoint = x => {
const ijk = publicAPI.getBucketIndices(x);
// The number and level of neighbors to search depends upon the tolerance and the bucket width.
// Here, we use InsertionLevel (default to 1 if not set)
const insertionLevel = model.insertionLevel ?? 1;
const numDivs = model.divisions;
for (let lvtk = 0; lvtk <= insertionLevel; lvtk++) {
const buckets = getBucketNeighbors(ijk, numDivs, lvtk);
for (let i = 0; i < buckets.length; i++) {
const nei = buckets[i];
const key = getBucketIndex(nei);
const bucket = model.hashTable.get(key);
if (bucket) {
for (let j = 0; j < bucket.length; j++) {
const ptId = bucket[j];
const pt = model.points.getPoint(ptId);
if (vtkMath.distance2BetweenPoints(x, pt) <= model.insertionTol2) {
return ptId;
}
}
}
}
}
return -1;
};
/**
* Find the closest point to a given point.
*
* @param {Vector3} x The point coordinates
* @returns The id of the closest point or -1 if not found
*/
publicAPI.findClosestPoint = x => {
publicAPI.buildLocator();
const ijk = publicAPI.getBucketIndices(x);
const numDivs = model.divisions;
let minDist2 = Number.MAX_VALUE;
let closest = -1;
const maxLevel = Math.max(...numDivs);
for (let level = 0; level < maxLevel && closest === -1; level++) {
const neighbors = getBucketNeighbors(ijk, numDivs, level);
for (let n = 0; n < neighbors.length; n++) {
const key = getBucketIndex(neighbors[n]);
const bucket = model.hashTable.get(key);
if (bucket) {
for (let b = 0; b < bucket.length; b++) {
const ptId = bucket[b];
const pt = model.dataSet.getPoints().getPoint(ptId);
const dist2 = vtkMath.distance2BetweenPoints(x, pt);
if (dist2 < minDist2) {
minDist2 = dist2;
closest = ptId;
}
}
}
}
}
return closest;
};
/**
* Find the closest point within a specified radius.
*
* @param {Number} radius The search radius
* @param {Vector3} x The point coordinates
* @param {Number} inputDataLength The length of the input data
* @returns {IFindClosestPointResult} The closest point result
*/
publicAPI.findClosestPointWithinRadius = (radius, x, inputDataLength = 0) => {
publicAPI.buildLocator();
let closest = -1;
const radius2 = radius * radius;
let minDist2 = 1.01 * radius2;
let dist2 = -1.0;
const ijk = publicAPI.getBucketIndices(x);
const key = getBucketIndex(ijk);
const bucket = model.hashTable.get(key);
if (bucket) {
for (let j = 0; j < bucket.length; j++) {
const ptId = bucket[j];
const pt = model.dataSet.getPoints().getPoint(ptId);
const d2 = vtkMath.distance2BetweenPoints(x, pt);
if (d2 < minDist2) {
closest = ptId;
minDist2 = d2;
dist2 = d2;
}
}
}
// Now, search only those buckets that are within a radius.
let refinedRadius;
let refinedRadius2;
if (minDist2 < radius2) {
refinedRadius = Math.sqrt(dist2);
refinedRadius2 = dist2;
} else {
refinedRadius = radius;
refinedRadius2 = radius2;
}
// Optionally restrict radius by inputDataLength and bounds
if (inputDataLength !== 0.0) {
const distance2ToDataBounds = vtkBoundingBox.distance2ToBounds(x, model.bounds);
const maxDistance = Math.sqrt(distance2ToDataBounds) + inputDataLength;
if (refinedRadius > maxDistance) {
refinedRadius = maxDistance;
refinedRadius2 = maxDistance * maxDistance;
}
}
// Compute radius levels for each dimension
const radiusLevels = [0, 0, 0];
for (let i = 0; i < 3; i++) {
radiusLevels[i] = Math.floor(refinedRadius / model.H[i]);
if (radiusLevels[i] > model.divisions[i] / 2) {
radiusLevels[i] = Math.floor(model.divisions[i] / 2);
}
}
let radiusLevel = Math.max(...radiusLevels);
if (radiusLevel === 0) {
radiusLevel = 1;
}
for (let ii = radiusLevel; ii >= 1; ii--) {
const currentRadius = refinedRadius;
// Build up a list of buckets that are arranged in rings
const buckets = getOverlappingBuckets(x, ijk, refinedRadius / ii, ii);
for (let i = 0; i < buckets.length; i++) {
const nei = buckets[i];
const d2ToBucket = distance2ToBucket(x, nei);
if (d2ToBucket < refinedRadius2) {
const key1 = getBucketIndex(nei);
const bucket1 = model.hashTable.get(key1);
if (bucket1) {
for (let j = 0; j < bucket1.length; j++) {
const ptId = bucket1[j];
const pt = model.dataSet.getPoints().getPoint(ptId);
if (pt) {
const d2 = vtkMath.distance2BetweenPoints(x, pt);
if (d2 < minDist2) {
closest = ptId;
minDist2 = d2;
refinedRadius = Math.sqrt(minDist2);
refinedRadius2 = minDist2;
dist2 = d2;
}
}
}
}
}
}
// Update ii according to refined radius
if (refinedRadius < currentRadius && ii > 2) {
ii = Math.floor(ii * (refinedRadius / currentRadius)) + 1;
if (ii < 2) {
ii = 2;
}
}
}
if (closest !== -1 && minDist2 <= radius * radius) {
dist2 = minDist2;
} else {
closest = -1;
}
return {
id: closest,
dist2
};
};
/**
* Find the closest inserted point to the given coordinates.
* @param {Vector3} x The query point
* @returns {Number} The id of the closest inserted point or -1 if not found
*/
publicAPI.findClosestInsertedPoint = x => {
// Check if point is within bounds
for (let i = 0; i < 3; i++) {
if (x[i] < model.bounds[2 * i] || x[i] > model.bounds[2 * i + 1]) {
return -1;
}
}
const ijk = publicAPI.getBucketIndices(x);
const numDivs = model.divisions;
let closest = -1;
let minDist2 = Number.MAX_VALUE;
let level = 0;
const maxLevel = Math.max(numDivs[0], numDivs[1], numDivs[2]);
const points = model.points;
// Search buckets and neighbors until closest found
for (; closest === -1 && level < maxLevel; level++) {
const neighbors = getBucketNeighbors(ijk, numDivs, level);
for (let i = 0; i < neighbors.length; i++) {
const nei = neighbors[i];
const cno = nei[0] + nei[1] * model.XD + nei[2] * model.sliceSize;
const bucket = model.hashTable.get(cno);
if (bucket) {
for (let j = 0; j < bucket.length; j++) {
const ptId = bucket[j];
const pt = points.getPoint(ptId);
const dist2 = vtkMath.distance2BetweenPoints(x, pt);
if (dist2 < minDist2) {
closest = ptId;
minDist2 = dist2;
}
}
}
}
}
// Refine: search next level neighbors that could be closer
const refineNeighbors = getBucketNeighbors(ijk, numDivs, level);
for (let i = 0; i < refineNeighbors.length; i++) {
const nei = refineNeighbors[i];
// Only consider neighbors that could possibly be closer
let dist2 = 0;
for (let j = 0; j < 3; j++) {
if (ijk[j] !== nei[j]) {
const MULTIPLES = ijk[j] > nei[j] ? nei[j] + 1 : nei[j];
const diff = model.bounds[2 * j] + MULTIPLES * model.H[j] - x[j];
dist2 += diff * diff;
}
}
if (dist2 < minDist2) {
const cno = nei[0] + nei[1] * model.XD + nei[2] * model.sliceSize;
const bucket = model.hashTable.get(cno);
if (bucket) {
for (let j = 0; j < bucket.length; j++) {
const ptId = bucket[j];
const pt = points.getPoint(ptId);
const d2 = vtkMath.distance2BetweenPoints(x, pt);
if (d2 < minDist2) {
closest = ptId;
minDist2 = d2;
}
}
}
}
}
return closest;
};
/**
* Get the points in the specified bucket.
* @param {*} x The point coordinates
* @returns {Number[]} The points in the bucket
*/
publicAPI.getPointsInBucket = x => {
publicAPI.buildLocator();
const key = publicAPI.getBucketIndex(x);
const bucket = model.hashTable.get(key);
if (!bucket) return []; // No points in this bucket
return bucket;
};
/**
* Free the search structure and reset the locator.
*/
publicAPI.freeSearchStructure = () => {
model.hashTable.clear();
model.points = vtkPoints.newInstance();
model.divisions = [50, 50, 50];
vtkMath.uninitializeBounds(model.bounds);
};
/**
* Compute performance factors based on the current model state.
*/
publicAPI.computePerformanceFactors = () => {
model.HX = model.H[0];
model.HY = model.H[1];
model.HZ = model.H[2];
model.FX = 1.0 / model.H[0];
model.FY = 1.0 / model.H[1];
model.FZ = 1.0 / model.H[2];
model.BX = model.bounds[0];
model.BY = model.bounds[2];
model.BZ = model.bounds[4];
model.XD = model.divisions[0];
model.YD = model.divisions[1];
model.ZD = model.divisions[2];
model.sliceSize = model.divisions[0] * model.divisions[1];
};
/**
* Generate a polydata representation of the point locator.
*
* @param {vtkPolyData} polydata The polydata to generate representation for
*/
publicAPI.generateRepresentation = polydata => {
if (!model.hashTable || model.hashTable.size === 0) {
vtkErrorMacro("Can't build representation, no data provided!");
return;
}
const facePts = [];
facePts.length = 4;
// Helper to add a face to polydata
function generateFace(face, i, j, k, pts, polys) {
// Compute the 8 corners of the bucket
const x0 = model.bounds[0] + i * model.HX;
const y0 = model.bounds[2] + j * model.HY;
const z0 = model.bounds[4] + k * model.HZ;
const x1 = x0 + model.HX;
const y1 = y0 + model.HY;
const z1 = z0 + model.HZ;
// Each face is defined by 4 points (quad)
// axis: 0=x, 1=y, 2=z
if (face === 0) {
// yz plane
facePts[0] = [x0, y0, z0];
facePts[1] = [x0, y1, z0];
facePts[2] = [x0, y1, z1];
facePts[3] = [x0, y0, z1];
} else if (face === 1) {
// xz plane
facePts[0] = [x0, y0, z0];
facePts[1] = [x1, y0, z0];
facePts[2] = [x1, y0, z1];
facePts[3] = [x0, y0, z1];
} else if (face === 2) {
// xy plane
facePts[0] = [x0, y0, z0];
facePts[1] = [x1, y0, z0];
facePts[2] = [x1, y1, z0];
facePts[3] = [x0, y1, z0];
}
// Add points to pts and get their ids
const ptIds = facePts.map(pt => pts.insertNextPoint(...pt));
// Add quad to polys
polys.insertNextCell([ptIds[0], ptIds[1], ptIds[2], ptIds[3]]);
}
// Prepare points and polys
const pts = vtkPoints.newInstance();
pts.allocate(5000);
const polys = vtkCellArray.newInstance();
polys.allocate(2048);
// We'll use a Set to avoid duplicate faces
const divisions = model.divisions;
const sliceSize = divisions[0] * divisions[1];
// Helper to check if a bucket exists
function hasBucket(i, j, k) {
if (i < 0 || i >= divisions[0] || j < 0 || j >= divisions[1] || k < 0 || k >= divisions[2]) {
return false;
}
const idx = i + j * divisions[0] + k * sliceSize;
return model.hashTable.has(idx);
}
// Loop over all buckets, creating appropriate faces
for (let k = 0; k < divisions[2]; k++) {
for (let j = 0; j < divisions[1]; j++) {
for (let i = 0; i < divisions[0]; i++) {
const idx = i + j * divisions[0] + k * sliceSize;
const inside = model.hashTable.has(idx);
// For each axis (0=x, 1=y, 2=z)
for (let axis = 0; axis < 3; axis++) {
let ni = i;
let nj = j;
let nk = k;
if (axis === 0) ni = i - 1;
if (axis === 1) nj = j - 1;
if (axis === 2) nk = k - 1;
const neighborInside = hasBucket(ni, nj, nk);
// If neighbor is out of bounds
if (ni < 0 || nj < 0 || nk < 0) {
if (inside) {
generateFace(axis, i, j, k, pts, polys);
}
} else if (neighborInside && !inside || !neighborInside && inside) {
generateFace(axis, i, j, k, pts, polys);
}
}
// Positive boundary faces
if (i + 1 >= divisions[0] && inside) {
generateFace(0, i + 1, j, k, pts, polys);
}
if (j + 1 >= divisions[1] && inside) {
generateFace(1, i, j + 1, k, pts, polys);
}
if (k + 1 >= divisions[2] && inside) {
generateFace(2, i, j, k + 1, pts, polys);
}
}
}
}
polydata.setPoints(pts);
polydata.setPolys(polys);
// polydata.squeeze();
};
}
// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------
function defaultValues(initialValues) {
return {
divisions: [50, 50, 50],
numberOfPointsPerBucket: 3,
bounds: [0, 0, 0, 0, 0, 0],
tolerance: 0.001,
automatic: true,
...initialValues
};
}
function extend(publicAPI, model, initialValues = {}) {
vtkAbstractPointLocator.extend(publicAPI, model, defaultValues(initialValues));
macro.setGet(publicAPI, model, ['numberOfPointsPerBucket', 'points']);
macro.setGetArray(publicAPI, model, ['divisions'], 3);
vtkMath.uninitializeBounds(model.bounds);
model.points = model.points || vtkPoints.newInstance();
model.hashTable = new Map();
model.H = [0, 0, 0]; // Bucket sizes in three dimensions
model.insertionPointId = 0; // ID for next point to be inserted
model.insertionTol2 = 0.0001; // Tolerance squared for point insertion
model.insertionLevel = 0; // Level of neighbors to search for insertion
vtkPointLocator(publicAPI, model);
}
const newInstance = macro.newInstance(extend, 'vtkPointLocator');
// ----------------------------------------------------------------------------
var vtkPointLocator$1 = {
newInstance,
extend
};
export { vtkPointLocator$1 as default, extend, newInstance };