Added member BoundingBoxf3 m_bounding_box to GLIndexedVertexArray and removed member BoundingBoxf3 bounding_box from GLVolume

This commit is contained in:
Enrico Turri 2019-07-01 13:26:06 +02:00
parent 4269c8b23c
commit 2356fe5a13
5 changed files with 30 additions and 58 deletions

View File

@ -328,11 +328,12 @@ bool GLVolume::is_left_handed() const
const BoundingBoxf3& GLVolume::transformed_bounding_box() const const BoundingBoxf3& GLVolume::transformed_bounding_box() const
{ {
assert(bounding_box.defined || bounding_box.min(0) >= bounding_box.max(0) || bounding_box.min(1) >= bounding_box.max(1) || bounding_box.min(2) >= bounding_box.max(2)); const BoundingBoxf3& box = bounding_box();
assert(box.defined || box.min(0) >= box.max(0) || box.min(1) >= box.max(1) || box.min(2) >= box.max(2));
if (m_transformed_bounding_box_dirty) if (m_transformed_bounding_box_dirty)
{ {
m_transformed_bounding_box = bounding_box.transformed(world_matrix()); m_transformed_bounding_box = box.transformed(world_matrix());
m_transformed_bounding_box_dirty = false; m_transformed_bounding_box_dirty = false;
} }
@ -350,9 +351,10 @@ BoundingBoxf3 GLVolume::transformed_convex_hull_bounding_box(const Transform3d &
{ {
return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ? return (m_convex_hull && m_convex_hull->stl.stats.number_of_facets > 0) ?
m_convex_hull->transformed_bounding_box(trafo) : m_convex_hull->transformed_bounding_box(trafo) :
bounding_box.transformed(trafo); bounding_box().transformed(trafo);
} }
void GLVolume::set_range(double min_z, double max_z) void GLVolume::set_range(double min_z, double max_z)
{ {
this->qverts_range.first = 0; this->qverts_range.first = 0;
@ -530,8 +532,6 @@ int GLVolumeCollection::load_object_volume(
v.set_color_from_model_volume(model_volume); v.set_color_from_model_volume(model_volume);
v.indexed_vertex_array.load_mesh(mesh); v.indexed_vertex_array.load_mesh(mesh);
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(); v.indexed_vertex_array.finalize_geometry();
v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx); v.composite_id = GLVolume::CompositeID(obj_idx, volume_idx, instance_idx);
if (model_volume->is_model_part()) if (model_volume->is_model_part())
@ -573,8 +573,6 @@ void GLVolumeCollection::load_object_auxiliary(
this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR)); this->volumes.emplace_back(new GLVolume((milestone == slaposBasePool) ? GLVolume::SLA_PAD_COLOR : GLVolume::SLA_SUPPORT_COLOR));
GLVolume& v = *this->volumes.back(); GLVolume& v = *this->volumes.back();
v.indexed_vertex_array.load_mesh(mesh); v.indexed_vertex_array.load_mesh(mesh);
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(); v.indexed_vertex_array.finalize_geometry();
v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first); v.composite_id = GLVolume::CompositeID(obj_idx, -int(milestone), (int)instance_idx.first);
v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id); v.geometry_id = std::pair<size_t, size_t>(timestamp, model_instance.id().id);
@ -648,8 +646,6 @@ int GLVolumeCollection::load_wipe_tower_preview(
v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0)); v.set_volume_offset(Vec3d(pos_x, pos_y, 0.0));
v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle)); v.set_volume_rotation(Vec3d(0., 0., (M_PI / 180.) * rotation_angle));
// finalize_geometry() clears the vertex arrays, therefore the bounding box has to be computed before finalize_geometry().
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(); v.indexed_vertex_array.finalize_geometry();
v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0); v.composite_id = GLVolume::CompositeID(obj_idx, 0, 0);
v.geometry_id.first = 0; v.geometry_id.first = 0;
@ -679,7 +675,7 @@ GLVolumeWithIdAndZList volumes_to_render(const GLVolumePtrs& volumes, GLVolumeCo
{ {
for (GLVolumeWithIdAndZ& volume : list) for (GLVolumeWithIdAndZ& volume : list)
{ {
volume.second.second = volume.first->bounding_box.transformed(view_matrix * volume.first->world_matrix()).max(2); volume.second.second = volume.first->bounding_box().transformed(view_matrix * volume.first->world_matrix()).max(2);
} }
std::sort(list.begin(), list.end(), std::sort(list.begin(), list.end(),
@ -1777,7 +1773,6 @@ bool GLCurvedArrow::on_init()
triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1); triangles.emplace_back(vertices_per_level, 2 * vertices_per_level + 1, vertices_per_level + 1);
m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles)); m_volume.indexed_vertex_array.load_mesh(TriangleMesh(vertices, triangles));
m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box();
m_volume.finalize_geometry(); m_volume.finalize_geometry();
return true; return true;
} }
@ -1815,7 +1810,6 @@ bool GLBed::on_init_from_file(const std::string& filename)
float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f }; float color[4] = { 0.235f, 0.235f, 0.235f, 1.0f };
set_color(color, 4); set_color(color, 4);
m_volume.bounding_box = m_volume.indexed_vertex_array.bounding_box();
m_volume.finalize_geometry(); m_volume.finalize_geometry();
return true; return true;

View File

@ -82,6 +82,7 @@ public:
this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved; this->vertices_and_normals_interleaved = rhs.vertices_and_normals_interleaved;
this->triangle_indices = rhs.triangle_indices; this->triangle_indices = rhs.triangle_indices;
this->quad_indices = rhs.quad_indices; this->quad_indices = rhs.quad_indices;
this->m_bounding_box = rhs.m_bounding_box;
this->setup_sizes(); this->setup_sizes();
return *this; return *this;
} }
@ -94,6 +95,7 @@ public:
this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved); this->vertices_and_normals_interleaved = std::move(rhs.vertices_and_normals_interleaved);
this->triangle_indices = std::move(rhs.triangle_indices); this->triangle_indices = std::move(rhs.triangle_indices);
this->quad_indices = std::move(rhs.quad_indices); this->quad_indices = std::move(rhs.quad_indices);
this->m_bounding_box = std::move(rhs.m_bounding_box);
this->setup_sizes(); this->setup_sizes();
return *this; return *this;
} }
@ -110,7 +112,7 @@ public:
size_t quad_indices_size; size_t quad_indices_size;
// IDs of the Vertex Array Objects, into which the geometry has been loaded. // IDs of the Vertex Array Objects, into which the geometry has been loaded.
// Zero if the VBOs are not used. // Zero if the VBOs are not sent to GPU yet.
unsigned int vertices_and_normals_interleaved_VBO_id; unsigned int vertices_and_normals_interleaved_VBO_id;
unsigned int triangle_indices_VBO_id; unsigned int triangle_indices_VBO_id;
unsigned int quad_indices_VBO_id; unsigned int quad_indices_VBO_id;
@ -135,6 +137,8 @@ public:
this->vertices_and_normals_interleaved.push_back(x); this->vertices_and_normals_interleaved.push_back(x);
this->vertices_and_normals_interleaved.push_back(y); this->vertices_and_normals_interleaved.push_back(y);
this->vertices_and_normals_interleaved.push_back(z); this->vertices_and_normals_interleaved.push_back(z);
m_bounding_box.merge(Vec3f(x, y, z).cast<double>());
}; };
inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) { inline void push_geometry(double x, double y, double z, double nx, double ny, double nz) {
@ -168,7 +172,7 @@ public:
void finalize_geometry(); void finalize_geometry();
// Release the geometry data, release OpenGL VBOs. // Release the geometry data, release OpenGL VBOs.
void release_geometry(); void release_geometry();
// Render either using an immediate mode, or the VBOs.
void render() const; void render() const;
void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const; void render(const std::pair<size_t, size_t> &tverts_range, const std::pair<size_t, size_t> &qverts_range) const;
@ -182,6 +186,7 @@ public:
this->vertices_and_normals_interleaved.clear(); this->vertices_and_normals_interleaved.clear();
this->triangle_indices.clear(); this->triangle_indices.clear();
this->quad_indices.clear(); this->quad_indices.clear();
this->m_bounding_box.reset();
this->setup_sizes(); this->setup_sizes();
} }
@ -194,27 +199,11 @@ public:
this->quad_indices.shrink_to_fit(); this->quad_indices.shrink_to_fit();
} }
BoundingBoxf3 bounding_box() const { const BoundingBoxf3& bounding_box() const { return m_bounding_box; }
BoundingBoxf3 bbox;
if (! this->vertices_and_normals_interleaved.empty()) {
bbox.defined = true;
bbox.min(0) = bbox.max(0) = this->vertices_and_normals_interleaved[3];
bbox.min(1) = bbox.max(1) = this->vertices_and_normals_interleaved[4];
bbox.min(2) = bbox.max(2) = this->vertices_and_normals_interleaved[5];
for (size_t i = 9; i < this->vertices_and_normals_interleaved.size(); i += 6) {
const float *verts = this->vertices_and_normals_interleaved.data() + i;
bbox.min(0) = std::min<coordf_t>(bbox.min(0), verts[0]);
bbox.min(1) = std::min<coordf_t>(bbox.min(1), verts[1]);
bbox.min(2) = std::min<coordf_t>(bbox.min(2), verts[2]);
bbox.max(0) = std::max<coordf_t>(bbox.max(0), verts[0]);
bbox.max(1) = std::max<coordf_t>(bbox.max(1), verts[1]);
bbox.max(2) = std::max<coordf_t>(bbox.max(2), verts[2]);
}
}
return bbox;
}
private: private:
BoundingBoxf3 m_bounding_box;
inline void setup_sizes() { inline void setup_sizes() {
vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size(); vertices_and_normals_interleaved_size = this->vertices_and_normals_interleaved.size();
triangle_indices_size = this->triangle_indices.size(); triangle_indices_size = this->triangle_indices.size();
@ -262,8 +251,6 @@ private:
mutable bool m_transformed_convex_hull_bounding_box_dirty; mutable bool m_transformed_convex_hull_bounding_box_dirty;
public: public:
// Bounding box of this volume, in unscaled coordinates.
BoundingBoxf3 bounding_box;
// Color of the triangles / quads held by this volume. // Color of the triangles / quads held by this volume.
float color[4]; float color[4];
// Color used to render this volume. // Color used to render this volume.
@ -328,6 +315,9 @@ public:
// Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer. // Offset into qverts & tverts, or offsets into indices stored into an OpenGL name_index_buffer.
std::vector<size_t> offsets; std::vector<size_t> offsets;
// Bounding box of this volume, in unscaled coordinates.
const BoundingBoxf3& bounding_box() const { return this->indexed_vertex_array.bounding_box(); }
void set_render_color(float r, float g, float b, float a); void set_render_color(float r, float g, float b, float a);
void set_render_color(const float* rgba, unsigned int size); void set_render_color(const float* rgba, unsigned int size);
// Sets render color in dependence of current state // Sets render color in dependence of current state
@ -536,7 +526,7 @@ public:
bool init() { return on_init(); } bool init() { return on_init(); }
bool init_from_file(const std::string& filename) { return on_init_from_file(filename); } bool init_from_file(const std::string& filename) { return on_init_from_file(filename); }
void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box.center()); } void center_around(const Vec3d& center) { m_volume.set_volume_offset(center - m_volume.bounding_box().center()); }
void set_color(const float* color, unsigned int size); void set_color(const float* color, unsigned int size);
const Vec3d& get_offset() const; const Vec3d& get_offset() const;
@ -547,7 +537,7 @@ public:
void set_scale(const Vec3d& scale); void set_scale(const Vec3d& scale);
const std::string& get_filename() const { return m_filename; } const std::string& get_filename() const { return m_filename; }
const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box; } const BoundingBoxf3& get_bounding_box() const { return m_volume.bounding_box(); }
const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); } const BoundingBoxf3& get_transformed_bounding_box() const { return m_volume.transformed_bounding_box(); }
void reset(); void reset();

View File

@ -3355,7 +3355,7 @@ arr::WipeTowerInfo GLCanvas3D::get_wipe_tower_info() const
wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"), wti.pos = Vec2d(m_config->opt_float("wipe_tower_x"),
m_config->opt_float("wipe_tower_y")); m_config->opt_float("wipe_tower_y"));
wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle"); wti.rotation = (M_PI/180.) * m_config->opt_float("wipe_tower_rotation_angle");
const BoundingBoxf3& bb = vol->bounding_box; const BoundingBoxf3& bb = vol->bounding_box();
wti.bb_size = Vec2d(bb.size()(0), bb.size()(1)); wti.bb_size = Vec2d(bb.size()(0), bb.size()(1));
break; break;
} }
@ -4474,7 +4474,6 @@ void GLCanvas3D::_load_print_toolpaths()
_3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume); _3DScene::extrusionentity_to_verts(print->skirt(), print_zs[i], Point(0, 0), volume);
} }
volume.bounding_box = volume.indexed_vertex_array.bounding_box();
volume.indexed_vertex_array.finalize_geometry(); volume.indexed_vertex_array.finalize_geometry();
} }
@ -4640,9 +4639,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
// Copy the content back to the old GLVolume. // Copy the content back to the old GLVolume.
vol.indexed_vertex_array = vol_new.indexed_vertex_array; vol.indexed_vertex_array = vol_new.indexed_vertex_array;
// Finalize a bounding box of the old GLVolume. // Clear the buffers, but keep them pre-allocated.
vol.bounding_box = vol.indexed_vertex_array.bounding_box();
// Clear the buffers, but keep them pre-allocated.
vol_new.indexed_vertex_array.clear(); vol_new.indexed_vertex_array.clear();
// Just make sure that clear did not clear the reserved memory. // Just make sure that clear did not clear the reserved memory.
vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve()); vol_new.indexed_vertex_array.reserve(ctxt.alloc_size_reserve());
@ -4650,8 +4647,7 @@ void GLCanvas3D::_load_print_object_toolpaths(const PrintObject& print_object, c
} }
} }
for (GLVolume *vol : vols) { for (GLVolume *vol : vols) {
vol->bounding_box = vol->indexed_vertex_array.bounding_box(); vol->indexed_vertex_array.shrink_to_fit();
vol->indexed_vertex_array.shrink_to_fit();
} }
}); });
@ -4812,8 +4808,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array); vol_new.indexed_vertex_array = std::move(vol.indexed_vertex_array);
// Copy the content back to the old GLVolume. // Copy the content back to the old GLVolume.
vol.indexed_vertex_array = vol_new.indexed_vertex_array; vol.indexed_vertex_array = vol_new.indexed_vertex_array;
// Finalize a bounding box of the old GLVolume.
vol.bounding_box = vol.indexed_vertex_array.bounding_box();
// Clear the buffers, but keep them pre-allocated. // Clear the buffers, but keep them pre-allocated.
vol_new.indexed_vertex_array.clear(); vol_new.indexed_vertex_array.clear();
// Just make sure that clear did not clear the reserved memory. // Just make sure that clear did not clear the reserved memory.
@ -4821,7 +4815,6 @@ void GLCanvas3D::_load_wipe_tower_toolpaths(const std::vector<std::string>& str_
} }
} }
for (GLVolume *vol : vols) { for (GLVolume *vol : vols) {
vol->bounding_box = vol->indexed_vertex_array.bounding_box();
vol->indexed_vertex_array.shrink_to_fit(); vol->indexed_vertex_array.shrink_to_fit();
} }
}); });
@ -5017,7 +5010,6 @@ void GLCanvas3D::_load_gcode_extrusion_paths(const GCodePreviewData& preview_dat
for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i)
{ {
GLVolume* volume = m_volumes.volumes[i]; GLVolume* volume = m_volumes.volumes[i];
volume->bounding_box = volume->indexed_vertex_array.bounding_box();
volume->indexed_vertex_array.finalize_geometry(); volume->indexed_vertex_array.finalize_geometry();
} }
} }
@ -5072,7 +5064,6 @@ void GLCanvas3D::_load_gcode_travel_paths(const GCodePreviewData& preview_data,
for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i) for (size_t i = initial_volumes_count; i < m_volumes.volumes.size(); ++i)
{ {
GLVolume* volume = m_volumes.volumes[i]; GLVolume* volume = m_volumes.volumes[i];
volume->bounding_box = volume->indexed_vertex_array.bounding_box();
volume->indexed_vertex_array.finalize_geometry(); volume->indexed_vertex_array.finalize_geometry();
} }
} }
@ -5306,7 +5297,6 @@ void GLCanvas3D::_load_gcode_retractions(const GCodePreviewData& preview_data)
} }
// finalize volumes and sends geometry to gpu // finalize volumes and sends geometry to gpu
volume->bounding_box = volume->indexed_vertex_array.bounding_box();
volume->indexed_vertex_array.finalize_geometry(); volume->indexed_vertex_array.finalize_geometry();
} }
} }
@ -5337,7 +5327,6 @@ void GLCanvas3D::_load_gcode_unretractions(const GCodePreviewData& preview_data)
} }
// finalize volumes and sends geometry to gpu // finalize volumes and sends geometry to gpu
volume->bounding_box = volume->indexed_vertex_array.bounding_box();
volume->indexed_vertex_array.finalize_geometry(); volume->indexed_vertex_array.finalize_geometry();
} }
} }
@ -5431,7 +5420,6 @@ void GLCanvas3D::_load_sla_shells()
for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) { for (unsigned int i = initial_volumes_count; i < m_volumes.volumes.size(); ++ i) {
GLVolume& v = *m_volumes.volumes[i]; GLVolume& v = *m_volumes.volumes[i];
// finalize volumes and sends geometry to gpu // finalize volumes and sends geometry to gpu
v.bounding_box = v.indexed_vertex_array.bounding_box();
v.indexed_vertex_array.finalize_geometry(); v.indexed_vertex_array.finalize_geometry();
// apply shift z // apply shift z
v.set_sla_shift_z(shift_z); v.set_sla_shift_z(shift_z);
@ -5523,7 +5511,7 @@ void GLCanvas3D::_update_toolpath_volumes_outside_state()
for (GLVolume* volume : m_volumes.volumes) for (GLVolume* volume : m_volumes.volumes)
{ {
volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box) : false; volume->is_outside = ((print_volume.radius() > 0.0) && volume->is_extrusion_path) ? !print_volume.contains(volume->bounding_box()) : false;
} }
} }

View File

@ -441,7 +441,7 @@ void ObjectManipulation::update_settings_value(const Selection& selection)
m_new_position = volume->get_volume_offset(); m_new_position = volume->get_volume_offset();
m_new_rotation = volume->get_volume_rotation() * (180. / M_PI); m_new_rotation = volume->get_volume_rotation() * (180. / M_PI);
m_new_scale = volume->get_volume_scaling_factor() * 100.; m_new_scale = volume->get_volume_scaling_factor() * 100.;
m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box.size()); m_new_size = volume->get_volume_transformation().get_scaling_factor().cwiseProduct(volume->bounding_box().size());
m_new_enabled = true; m_new_enabled = true;
} }
else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot)) else if (obj_list->multiple_selection() || obj_list->is_selected(itInstanceRoot))
@ -721,8 +721,8 @@ void ObjectManipulation::change_size_value(int axis, double value)
Vec3d ref_size = m_cache.size; Vec3d ref_size = m_cache.size;
if (selection.is_single_volume() || selection.is_single_modifier()) if (selection.is_single_volume() || selection.is_single_modifier())
ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box.size(); ref_size = selection.get_volume(*selection.get_volume_idxs().begin())->bounding_box().size();
else if (selection.is_single_full_instance()) else if (selection.is_single_full_instance())
ref_size = m_world_coordinates ? ref_size = m_world_coordinates ?
selection.get_unscaled_instance_bounding_box().size() : selection.get_unscaled_instance_bounding_box().size() :
(*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size(); (*wxGetApp().model_objects())[selection.get_volume(*selection.get_volume_idxs().begin())->object_idx()]->raw_mesh_bounding_box().size();

View File

@ -136,7 +136,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const
for (unsigned int idx : idxs) for (unsigned int idx : idxs)
{ {
const GLVolume* vol = selection.get_volume(idx); const GLVolume* vol = selection.get_volume(idx);
m_box.merge(vol->bounding_box.transformed(vol->get_volume_transformation().get_matrix())); m_box.merge(vol->bounding_box().transformed(vol->get_volume_transformation().get_matrix()));
} }
// gets transform from first selected volume // gets transform from first selected volume
@ -151,7 +151,7 @@ void GLGizmoScale3D::on_render(const Selection& selection) const
else if (single_volume) else if (single_volume)
{ {
const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin()); const GLVolume* v = selection.get_volume(*selection.get_volume_idxs().begin());
m_box = v->bounding_box; m_box = v->bounding_box();
m_transform = v->world_matrix(); m_transform = v->world_matrix();
angles = Geometry::extract_euler_angles(m_transform); angles = Geometry::extract_euler_angles(m_transform);
// consider rotation+mirror only components of the transform for offsets // consider rotation+mirror only components of the transform for offsets