@awayjs/graphics
Version:
AwayJS graphics classes
236 lines (210 loc) • 8.49 kB
text/typescript
import { assert } from './utilities';
import { GraphicsPath } from '../draw/GraphicsPath';
import { DataBuffer } from './DataBuffer';
import { PathCommand } from './ShapeData';
export class PathSegment {
private static _counter = 0;
public id;
private startPoint: string;
private endPoint: string;
public isValidFill: boolean=true;
constructor(public commands: DataBuffer, public data: DataBuffer, public morphData: DataBuffer,
public prev: PathSegment, public next: PathSegment, public isReversed: boolean) {
this.id = PathSegment._counter++;
}
static FromDefaults(isMorph: boolean) {
const commands = new DataBuffer();
const data = new DataBuffer();
commands.endian = data.endian = 'auto';
let morphData: any = null;
if (isMorph) {
morphData = new DataBuffer();
morphData.endian = 'auto';
}
return new PathSegment(commands, data, morphData, null, null, false);
}
moveTo(x: number, y: number) {
this.commands.writeUnsignedByte(PathCommand.MoveTo);
this.data.write2Ints(x, y);
}
morphMoveTo(x: number, y: number, mx: number, my: number) {
this.moveTo(x, y);
this.morphData.write2Ints(mx, my);
}
lineTo(x: number, y: number) {
this.commands.writeUnsignedByte(PathCommand.LineTo);
this.data.write2Ints(x, y);
}
morphLineTo(x: number, y: number, mx: number, my: number) {
this.lineTo(x, y);
this.morphData.write2Ints(mx, my);
}
curveTo(cpx: number, cpy: number, x: number, y: number) {
this.commands.writeUnsignedByte(PathCommand.CurveTo);
this.data.write4Ints(cpx, cpy, x, y);
}
morphCurveTo(cpx: number, cpy: number, x: number, y: number,
mcpx: number, mcpy: number, mx: number, my: number) {
this.curveTo(cpx, cpy, x, y);
this.morphData.write4Ints(mcpx, mcpy, mx, my);
}
/**
* Returns a shallow copy of the segment1 with the "isReversed" flag set.
* Reversed segments play themselves back in reverse when they're merged into the final
* non-segmented path1.
* Note: Don't modify the original, or the reversed copy, after this operation!
*/
toReversed(): PathSegment {
assert(!this.isReversed);
return new PathSegment(this.commands, this.data, this.morphData, null, null, true);
}
clone(): PathSegment {
return new PathSegment(this.commands, this.data, this.morphData, null, null, this.isReversed);
}
storeStartAndEnd() {
const data = this.data.ints;
const endPoint1 = data[0] + ',' + data[1];
const endPoint2Offset = (this.data.length >> 2) - 2;
const endPoint2 = data[endPoint2Offset] + ',' + data[endPoint2Offset + 1];
if (!this.isReversed) {
this.startPoint = endPoint1;
this.endPoint = endPoint2;
} else {
this.startPoint = endPoint2;
this.endPoint = endPoint1;
}
}
connectsTo(other: PathSegment): boolean {
//assert(other !== this);
if (other === this)
return false;
assert(this.endPoint);
assert(other.startPoint);
return this.endPoint === other.startPoint;
}
startConnectsTo(other: PathSegment): boolean {
if (other === this)
return false;
// assert(other !== this);
return this.startPoint === other.startPoint;
}
flipDirection() {
let tempPoint = '';
tempPoint = this.startPoint;
this.startPoint = this.endPoint;
this.endPoint = tempPoint;
this.isReversed = !this.isReversed;
}
serializeAJS(shape: GraphicsPath, morphShape: GraphicsPath, lastPosition: {x: number; y: number}) {
// mark that this is morp source, this should disable some optimisation for regular shapes
if (morphShape) {
morphShape.morphSource = shape.morphSource = true;
}
//console.log("serializeAJS segment1");
if (this.isReversed) {
this._serializeReversedAJS(shape, morphShape, lastPosition);
return;
}
const commands = this.commands.bytes;
// Note: this *must* use `this.data.length`, because buffers will have padding.
const dataLength = this.data.length >> 2;
const morphData = this.morphData ? this.morphData.ints : null;
const data = this.data.ints;
// If the segment1's first moveTo goes to the current coordinates, we have to skip it.
let offset = 0;
if (data[0] === lastPosition.x && data[1] === lastPosition.y && !morphShape) {
offset++;
}
const commandsCount = this.commands.length;
let dataPosition = offset * 2;
for (let i = offset; i < commandsCount; i++) {
switch (commands[i]) {
case PathCommand.MoveTo:
//console.log("moveTo",data[dataPosition]/20, data[dataPosition+1]/20);
shape.moveTo(data[dataPosition] / 20, data[dataPosition + 1] / 20);
if (morphShape) {
morphShape.moveTo(morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20);
}
break;
case PathCommand.LineTo:
//console.log("lineTo",data[dataPosition]/20, data[dataPosition+1]/20);
shape.lineTo(data[dataPosition] / 20, data[dataPosition + 1] / 20);
if (morphShape) {
morphShape.lineTo(morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20);
}
break;
case PathCommand.CurveTo:
//console.log("curveTo",data[dataPosition]/20, data[dataPosition+1]/20,data[dataPosition+2]/20, data[dataPosition+3]/20);
shape.curveTo(data[dataPosition] / 20, data[dataPosition + 1] / 20,data[dataPosition + 2] / 20, data[dataPosition + 3] / 20);
if (morphShape) {
morphShape.curveTo(morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20,morphData[dataPosition + 2] / 20, morphData[dataPosition + 3] / 20);
}
//shape.curveTo(data[dataPosition]/20, data[dataPosition+1]/20, data[dataPosition+2]/20, data[dataPosition+3]/20 );
dataPosition += 2;
break;
}
dataPosition += 2;
}
//assert(dataPosition === dataLength);
lastPosition.x = data[dataLength - 2];
lastPosition.y = data[dataLength - 1];
}
private _serializeReversedAJS(shape: GraphicsPath, morphShape: GraphicsPath, lastPosition: {x: number; y: number}) {
//console.log("_serializeReversedAJS segment1");
// For reversing the fill0 segments, we rely on the fact that each segment1
// starts with a moveTo. We first write a new moveTo with the final drawing command's
// target coordinates (if we don't skip it, see below). For each of the following
// commands, we take the coordinates of the command originally *preceding*
// it as the new target coordinates. The final coordinates we target will be
// the ones from the original first moveTo.
// Note: these *must* use `this.{data,commands}.length`, because buffers will have padding.
const commandsCount = this.commands.length;
let dataPosition = (this.data.length >> 2) - 2;
const commands = this.commands.bytes;
assert(commands[0] === PathCommand.MoveTo);
const data = this.data.ints;
const morphData = this.morphData ? this.morphData.ints : null;
// Only write the first moveTo if it doesn't go to the current coordinates.
if (data[dataPosition] !== lastPosition.x || data[dataPosition + 1] !== lastPosition.y) {
shape.moveTo(data[dataPosition] / 20, data[dataPosition + 1] / 20);
if (morphShape) {
morphShape.moveTo(morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20);
}
}
if (commandsCount === 1) {
lastPosition.x = data[0];
lastPosition.y = data[1];
return;
}
for (let i = commandsCount; i-- > 1;) {
dataPosition -= 2;
switch (commands[i]) {
case PathCommand.MoveTo:
//console.log("moveTo",data[dataPosition]/20, data[dataPosition+1]/20);
shape.moveTo(data[dataPosition] / 20, data[dataPosition + 1] / 20);
if (morphShape) {
morphShape.moveTo(morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20);
}
break;
case PathCommand.LineTo:
//console.log("lineTo",data[dataPosition]/20, data[dataPosition+1]/20);
shape.lineTo(data[dataPosition] / 20, data[dataPosition + 1] / 20);
if (morphShape) {
morphShape.lineTo(morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20);
}
break;
case PathCommand.CurveTo:
dataPosition -= 2;
//console.log("curveTo",data[dataPosition+2]/20, data[dataPosition+3]/20,data[dataPosition]/20, data[dataPosition+1]/20);
shape.curveTo(data[dataPosition + 2] / 20, data[dataPosition + 3] / 20,data[dataPosition] / 20, data[dataPosition + 1] / 20);
if (morphShape) {
morphShape.curveTo(morphData[dataPosition + 2] / 20, morphData[dataPosition + 3] / 20,morphData[dataPosition] / 20, morphData[dataPosition + 1] / 20);
}
break;
}
}
//assert(dataPosition === 0);
lastPosition.x = data[0];
lastPosition.y = data[1];
}
}