Skip to content

Commit

Permalink
Add selection updates to undo history (#194)
Browse files Browse the repository at this point in the history
  • Loading branch information
slimbuck authored Sep 25, 2024
1 parent 934772c commit 5c29452
Show file tree
Hide file tree
Showing 18 changed files with 544 additions and 486 deletions.
2 changes: 1 addition & 1 deletion src/camera.ts
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ class Camera extends Element {
const pickId = this.pick(sx, sy);

if (pickId !== -1) {
splat.getSplatWorldPosition(pickId, vec);
splat.calcSplatWorldPosition(pickId, vec);

// create a plane at the world position facing perpendicular to the camera
plane.setFromPointNormal(vec, this.entity.forward);
Expand Down
10 changes: 2 additions & 8 deletions src/edit-history.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,5 @@
import { Events } from './events';

interface EditOp {
name: string;
do(): void;
undo(): void;
destroy?(): void;
}
import { EditOp } from './edit-ops';

class EditHistory {
history: EditOp[] = [];
Expand Down Expand Up @@ -76,4 +70,4 @@ class EditHistory {
}
}

export { EditHistory, EditOp };
export { EditHistory };
248 changes: 180 additions & 68 deletions src/edit-ops.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
import { GSplatData, Quat, Vec3 } from 'playcanvas';
import { Scene } from './scene';
import { Mat4, Quat, Vec3 } from 'playcanvas';
import { Splat } from './splat';
import { State } from './splat-state';

enum State {
selected = 1,
hidden = 2,
deleted = 4
interface EditOp {
name: string;
do(): void;
undo(): void;
destroy?(): void;
}

// build a splat index based on a boolean predicate
const buildIndex = (splatData: GSplatData, pred: (i: number) => boolean) => {
let numSplats = 0;
for (let i = 0; i < splatData.numSplats; ++i) {
if (pred(i)) numSplats++;
// build an index array based on a boolean predicate over indices
const buildIndex = (total: number, pred: (i: number) => boolean) => {
let num = 0;
for (let i = 0; i < total; ++i) {
if (pred(i)) num++;
}

const result = new Uint32Array(numSplats);
const result = new Uint32Array(num);
let idx = 0;
for (let i = 0; i < splatData.numSplats; ++i) {
for (let i = 0; i < total; ++i) {
if (pred(i)) {
result[idx++] = i;
}
Expand All @@ -26,36 +27,47 @@ const buildIndex = (splatData: GSplatData, pred: (i: number) => boolean) => {
return result;
};

class DeleteSelectionEditOp {
name = 'deleteSelection';
type filterFunc = (state: number, index: number) => boolean;
type doFunc = (state: number) => number;
type undoFunc = (state: number) => number;

class StateOp {
splat: Splat;
indices: Uint32Array;
doIt: doFunc;
undoIt: undoFunc;
updateFlags: number;

constructor(splat: Splat) {
constructor(splat: Splat, filter: filterFunc, doIt: doFunc, undoIt: undoFunc, updateFlags = State.selected) {
const splatData = splat.splatData;
const state = splatData.getProp('state') as Uint8Array;
const indices = buildIndex(splatData, (i) => !!(state[i] & State.selected));
const indices = buildIndex(splatData.numSplats, (i) => filter(state[i], i));

this.splat = splat;
this.indices = indices;
this.doIt = doIt;
this.undoIt = undoIt;
this.updateFlags = updateFlags;
}

do() {
const splatData = this.splat.splatData;
const state = splatData.getProp('state') as Uint8Array;
for (let i = 0; i < this.indices.length; ++i) {
state[this.indices[i]] |= State.deleted;
const idx = this.indices[i];
state[idx] = this.doIt(state[idx]);
}
this.splat.updateState(true);
this.splat.updateState(this.updateFlags);
}

undo() {
const splatData = this.splat.splatData;
const state = splatData.getProp('state') as Uint8Array;
for (let i = 0; i < this.indices.length; ++i) {
state[this.indices[i]] &= ~State.deleted;
const idx = this.indices[i];
state[idx] = this.undoIt(state[idx]);
}
this.splat.updateState(true);
this.splat.updateState(this.updateFlags);
}

destroy() {
Expand All @@ -64,41 +76,117 @@ class DeleteSelectionEditOp {
}
}

class ResetEditOp {
name = 'reset';
splat: Splat;
indices: Uint32Array;
class SelectAllOp extends StateOp {
name = 'selectAll';

constructor(splat: Splat) {
const splatData = splat.splatData;
const state = splatData.getProp('state') as Uint8Array;
const indices = buildIndex(splatData, (i) => !!(state[i] & State.deleted));
super(splat,
(state) => state === 0,
(state) => state | State.selected,
(state) => state & (~State.selected)
);
}
}

this.splat = splat;
this.indices = indices;
class SelectNoneOp extends StateOp {
name = 'selectNone';

constructor(splat: Splat) {
super(splat,
(state) => state === State.selected,
(state) => state & (~State.selected),
(state) => state | State.selected
);
}
}

do() {
const splatData = this.splat.splatData;
const state = splatData.getProp('state') as Uint8Array;
for (let i = 0; i < this.indices.length; ++i) {
state[this.indices[i]] &= ~State.deleted;
}
this.splat.updateState(true);
class SelectInvertOp extends StateOp {
name = 'selectInvert';

constructor(splat: Splat) {
super(splat,
(state) => (state & (State.hidden | State.deleted)) === 0,
(state) => state ^ State.selected,
(state) => state ^ State.selected
);
}
}

undo() {
const splatData = this.splat.splatData;
const state = splatData.getProp('state') as Uint8Array;
for (let i = 0; i < this.indices.length; ++i) {
state[this.indices[i]] |= State.deleted;
}
this.splat.updateState(true);
class SelectOp extends StateOp {
name = 'selectOp';

constructor(splat: Splat, op: 'add'|'remove'|'set', filter: (i: number) => boolean) {
const filterFunc = {
add: (state: number, index: number) => (state === 0) && filter(index),
remove: (state: number, index: number) => (state === State.selected) && filter(index),
set: (state: number, index: number) => (state === State.selected) !== filter(index),
};

const doIt = {
add: (state: number) => state | State.selected,
remove: (state: number) => state & (~State.selected),
set: (state: number) => state ^ State.selected
};

const undoIt = {
add: (state: number) => state & (~State.selected),
remove: (state: number) => state | State.selected,
set: (state: number) => state ^ State.selected
};

super(splat, filterFunc[op], doIt[op], undoIt[op]);
}
}

destroy() {
this.splat = null;
this.indices = null;
class HideSelectionOp extends StateOp {
name = 'hideSelection';

constructor(splat: Splat) {
super(splat,
(state) => state === State.selected,
(state) => state | State.hidden,
(state) => state & (~State.hidden),
State.hidden
);
}
}

class UnhideAllOp extends StateOp {
name = 'unhideAll';

constructor(splat: Splat) {
super(splat,
(state) => (state & (State.hidden | State.deleted)) === State.hidden,
(state) => state & (~State.hidden),
(state) => state | State.hidden,
State.hidden
);
}
}

class DeleteSelectionOp extends StateOp {
name = 'deleteSelection';

constructor(splat: Splat) {
super(splat,
(state) => state === State.selected,
(state) => state | State.deleted,
(state) => state & (~State.deleted),
State.deleted
);
}
}

class ResetOp extends StateOp {
name = 'reset';

constructor(splat: Splat) {
super(splat,
(state) => (state & State.deleted) !== 0,
(state) => state & (~State.deleted),
(state) => state | State.deleted,
State.deleted
);
}
}

Expand All @@ -108,41 +196,65 @@ interface EntityTransform {
scale?: Vec3;
}

interface EntityOp {
splat: Splat;
old: EntityTransform;
new: EntityTransform;
}

class EntityTransformOp {
name = 'entityTransform';
entityOps: EntityOp[];

constructor(entityOps: EntityOp[]) {
this.entityOps = entityOps;
splat: Splat;
oldt: EntityTransform;
newt: EntityTransform;

constructor(options: { splat: Splat, oldt: EntityTransform, newt: EntityTransform }) {
this.splat = options.splat;
this.oldt = options.oldt;
this.newt = options.newt;
}

do() {
this.entityOps.forEach((entityOp) => {
entityOp.splat.move(entityOp.new.position, entityOp.new.rotation, entityOp.new.scale);
});
this.splat.move(this.newt.position, this.newt.rotation, this.newt.scale);
}

undo() {
this.entityOps.forEach((entityOp) => {
entityOp.splat.move(entityOp.old.position, entityOp.old.rotation, entityOp.old.scale);
});
this.splat.move(this.oldt.position, this.oldt.rotation, this.oldt.scale);
}

destroy() {
this.entityOps = [];
this.splat = null;
this.oldt = null;
this.newt = null;
}
}

class SetPivotOp {
name = "setPivot";
splat: Splat;
oldPivot: Vec3;
newPivot: Vec3;

constructor(splat: Splat, oldPivot: Vec3, newPivot: Vec3) {
this.splat = splat;
this.oldPivot = oldPivot;
this.newPivot = newPivot;
}

do() {
this.splat.setPivot(this.newPivot);
}

undo() {
this.splat.setPivot(this.oldPivot);
}
}

export {
State,
DeleteSelectionEditOp,
ResetEditOp,
EntityOp,
EntityTransformOp
EditOp,
SelectAllOp,
SelectNoneOp,
SelectInvertOp,
SelectOp,
HideSelectionOp,
UnhideAllOp,
DeleteSelectionOp,
ResetOp,
EntityTransformOp,
SetPivotOp
};
Loading

0 comments on commit 5c29452

Please sign in to comment.