import { Point, MathConsts } from '@awayjs/core'; import { ImageSampler, AttributesBuffer, AttributesView, Float2Attributes } from '@awayjs/stage'; import { Style, LineElements, LineScaleMode, TriangleElements } from '@awayjs/renderer'; import { Shape } from '../renderables/Shape'; import { JointStyle } from './JointStyle'; import { GraphicsPath } from './GraphicsPath'; import { GraphicsPathCommand } from './GraphicsPathCommand'; import { GraphicsFactoryHelper } from './GraphicsFactoryHelper'; import { GraphicsStrokeStyle } from './GraphicsStrokeStyle'; import { Graphics } from '../Graphics'; import { GraphicsFactoryFills, UnpackFillStyle } from './GraphicsFactoryFills'; /** * The Graphics class contains a set of methods that you can use to create a * vector shape. Display objects that support drawing include Sprite and Shape * objects. Each of these classes includes a graphics property * that is a Graphics object. The following are among those helper functions * provided for ease of use: drawRect(), * drawRoundRect(), drawCircle(), and * drawEllipse(). * *

You cannot create a Graphics object directly from ActionScript code. If * you call new Graphics(), an exception is thrown.

* *

The Graphics class is final; it cannot be subclassed.

*/ export class GraphicsFactoryStrokes { public static draw_pathes(targetGraphics: Graphics, clear: boolean = false) { //return; const paths = targetGraphics.queued_stroke_pathes; const len = paths.length; let shape: Shape; for (let i = 0; i < len; i++) { const path = paths[i]; const style = (>path.style); shape = targetGraphics.popEmptyStrokeShape(); path.prepare(); // if (targetGraphics.scaleStrokes != null) { //use LineElements const elements = this.fillLineElements( [path], false, //material.curves, style.scaleMode, shape?.elements ); if (!elements) continue; elements.scaleMode = style.scaleMode; elements.half_thickness = style.half_thickness; const data = { style: new Style(), sampler: new ImageSampler(), material: null }; UnpackFillStyle[path.style.fillStyle.data_type](path.style.fillStyle, data); if (shape) { shape.material = data.material; shape.style = data.style; } else { shape = Shape.getShape(elements, data.material, data.style); } targetGraphics.addShapeInternal(shape); } targetGraphics.queued_stroke_pathes.length = 0; if (clear) { targetGraphics._active_stroke_path = null; targetGraphics._lastStroke = null; } else if (shape) { targetGraphics.queued_stroke_pathes.push(targetGraphics._active_stroke_path); targetGraphics._lastStroke = shape; } } // public static updateStrokesForShape(shape:Shape, scale:Vector3D, scaleMode:string ){ // //return; // var elements:TriangleElements = shape.elements; // var final_vert_list:Array=[]; // GraphicsFactoryStrokes.draw_path([graphicsPath], final_vert_list, false, scale, scaleMode); // elements.concatenatedBuffer.count = final_vert_list.length/2; // elements.setPositions(final_vert_list); // elements.invalidate(); // } public static fillLineElements( graphic_pathes: Array, curves: boolean, scaleMode: LineScaleMode = LineScaleMode.NORMAL, target: LineElements = null ): LineElements { const finalVerts: number[] = []; const final_thickness_list: number[] = []; let data: number[]; let end_x: number = 0; let end_y: number = 0; let prev_x: number = 0; let prev_y: number = 0; let pos_count: number = 0; let thickness_count: number = 0; for (let cp = 0, l0 = graphic_pathes.length; cp < l0; cp++) { const path = graphic_pathes[cp]; path.prepare(); const positions = path._positions; const strokeStyle = path.stroke; const half_thickness = (scaleMode != LineScaleMode.HAIRLINE) ? strokeStyle.half_thickness : 0.5; for (let k = 0, l1 = positions.length; k < l1; k++) { //commands = contour_commands[k]; data = positions[k]; prev_x = data[0]; prev_y = data[1]; for (let i = 2, l2 = data.length; i < l2; i += 2) { end_x = data[i + 0]; end_y = data[i + 1]; const near = GraphicsFactoryFills.nearest(end_x, end_y, prev_x, prev_y); // if the points are the same, we dont need to do anything. if (!near) { finalVerts[pos_count++] = prev_x; finalVerts[pos_count++] = prev_y; finalVerts[pos_count++] = 0; finalVerts[pos_count++] = end_x; finalVerts[pos_count++] = end_y; finalVerts[pos_count++] = 0; final_thickness_list[thickness_count++] = half_thickness; } prev_x = end_x; prev_y = end_y; } } } if (finalVerts.length == 0) return null; const elements = target || new LineElements(new AttributesBuffer()); elements.setPositions(finalVerts); elements.setThickness(final_thickness_list); return elements; } public static getTriangleElements( graphic_pathes: Array, curves: boolean, scaleMode: LineScaleMode = LineScaleMode.NORMAL): TriangleElements { const final_vert_list: number[] = []; const len = graphic_pathes.length; let positions: number[][]; let strokeStyle: GraphicsStrokeStyle; let data: number[]; let i: number = 0; let k: number = 0; const tmp_dir_point: Point = new Point(); const last_dir_vec: Point = new Point(); const tmp_point: Point = new Point(); const tmp_point2: Point = new Point(); const tmp_point3: Point = new Point(); let end_x: number = 0; let end_y: number = 0; let prev_x: number = 0; let prev_y: number = 0; let right_point_x: number = 0; let right_point_y: number = 0; let left_point_x: number = 0; let left_point_y: number = 0; let right_point_merged_x: number = 0; let right_point_merged_y: number = 0; let left_point_merged_x: number = 0; let left_point_merged_y: number = 0; let left_point_contour_x: number = 0; let left_point_contour_y: number = 0; let left_point_contour_prev_x: number = 0; let left_point_contour_prev_y: number = 0; let right_point_contour_x: number = 0; let right_point_contour_y: number = 0; let right_point_contour_prev_x: number = 0; let right_point_contour_prev_y: number = 0; let start_right_x: number = 0; let start_right_y: number = 0; let start_left_x: number = 0; let start_left_y: number = 0; let end_right_x: number = 0; let end_right_y: number = 0; let end_left_x: number = 0; let end_left_y: number = 0; let prev_normal_x: number; let prev_normal_y: number; let addJoints: boolean = true; let add_segment: boolean = false; let closed: boolean = false; let new_dir: number = 0; let dir_delta: number = 0; let last_direction: number = 0; let distance_miter: number = 0; let half_angle: number = 0; let distanceX: number = 0; let distanceY: number = 0; let half_thicknessX: number = 0; let half_thicknessY: number = 0; let new_cmds: number[] = []; let new_pnts: number[] = []; let new_cmds_cnt: number = 0; let new_pnts_cnt: number = 0; //console.log("process stroke"); let cp = 0; for (cp = 0; cp < len; cp++) { positions = graphic_pathes[cp]._positions; strokeStyle = graphic_pathes[cp].stroke; half_thicknessX = strokeStyle.half_thickness; half_thicknessY = strokeStyle.half_thickness; //console.log("process contour", positions); if (scaleMode == LineScaleMode.NORMAL) { /* if((half_thicknessX*scale.x)<=0.5) half_thicknessX=0.5*(1/scale.x); if((half_thicknessY*scale.y)<=0.5) half_thicknessY=0.5*(1/scale.y); */ } // else if(scaleMode==LineScaleMode.NONE){ // half_thicknessX*=(1/scale.x); // half_thicknessY*=(1/scale.y); // } // if(strokeStyle.scaleMode==LineScaleMode.HAIRLINE){ // half_thicknessX=0.5*(1/scale.x); // half_thicknessY=0.5*(1/scale.y); // } for (k = 0; k < positions.length; k++) { //commands = contour_commands[k]; data = positions[k]; // check if the path is closed. if yes, than set the last_dir_vec from last segment closed = true; if ((data[0] != data[data.length - 2]) || (data[1] != data[data.length - 1])) closed = false; else { last_dir_vec.x = data[data.length - 2] - data[data.length - 4]; last_dir_vec.y = data[data.length - 1] - data[data.length - 3]; last_dir_vec.normalize(); last_direction = Math.atan2(last_dir_vec.y, last_dir_vec.x) * MathConsts.RADIANS_TO_DEGREES; //console.log("Path is closed, we set initial direction: "+last_direction); } prev_x = data[0]; prev_y = data[1]; new_cmds = []; new_pnts = []; new_cmds_cnt = 0; new_pnts_cnt = 0; prev_normal_x = -1 * last_dir_vec.y; prev_normal_y = last_dir_vec.x; for (i = 2; i < data.length; i += 2) { end_x = data[i]; end_y = data[i + 1]; //half_thicknessX=bkphalf_thicknessX*(i/data.length); //half_thicknessY=bkphalf_thicknessY*(i/data.length); // if the points are the same, we dont need to do anything. if ((end_x != prev_x) || (end_y != prev_y)) { //get the directional vector and the direction for this segment tmp_dir_point.x = end_x - prev_x; tmp_dir_point.y = end_y - prev_y; tmp_dir_point.normalize(); new_dir = Math.atan2(tmp_dir_point.y, tmp_dir_point.x) * MathConsts.RADIANS_TO_DEGREES; // get the difference in angle to the last segment dir_delta = new_dir - last_direction; if (dir_delta > 180) { dir_delta -= 360; } if (dir_delta < -180) { dir_delta += 360; } last_direction = new_dir; // rotate direction around 90 degree tmp_point.x = -1 * tmp_dir_point.y; tmp_point.y = tmp_dir_point.x; // find the 2 points left and right of the segments end-point right_point_x = prev_x + (tmp_point.x * half_thicknessX); right_point_y = prev_y + (tmp_point.y * half_thicknessY); left_point_x = prev_x - (tmp_point.x * half_thicknessX); left_point_y = prev_y - (tmp_point.y * half_thicknessY); add_segment = false; // check if this is the first segment, and the path is not closed // in this case, we can just set the points to the contour points if ((i == 2) && (!closed)) { //console.log("segment "+i+"Path is not closed, we can just add the first segment") add_segment = true; } else { // dir_delta delta is the difference in direction between the segments // 2 segments forming a straight line have a dir_delta of 0 // a segments that goes straight back would have a dir_delta of 180 or -180 // we want to convert this into a angle where 0 means going back // and 180 means straight forward. half_angle = (180 - (dir_delta)); if (dir_delta < 0) { half_angle = (-180 - (dir_delta)); } if ((dir_delta == 0) || (Math.abs(dir_delta) == 180)) { // straight line (back or forth) // only add segment if this is the first add_segment = (i == 2); } else if (Math.abs(half_angle) < 5) { // line going back with very steep angle add_segment = true; } else { add_segment = true; // half_angle is the angle need to rotate our segments direction with // in order to have a direction vector that points from // original point to the merged contour points half_angle = half_angle * -0.5 * MathConsts.DEGREES_TO_RADIANS; // get the direction vector that splits the angle between the 2 segments in half. // eslint-disable-next-line max-len tmp_point2.x = tmp_dir_point.x * Math.cos(half_angle) + tmp_dir_point.y * Math.sin(half_angle); // eslint-disable-next-line max-len tmp_point2.y = tmp_dir_point.y * Math.cos(half_angle) - tmp_dir_point.x * Math.sin(half_angle); tmp_point2.normalize(); // calculate the distance that we need to move original point // with direction-vector calculated above // very steep angles result in impossible values for distance // we need to catch those cases and set sensible fallback for distance if (Math.abs(dir_delta) <= 1 || Math.abs(dir_delta) >= 359 || (Math.abs(dir_delta) >= 179 && Math.abs(dir_delta) <= 181)) { distanceX = (dir_delta < 0) ? half_thicknessX : -half_thicknessX; distanceY = (dir_delta < 0) ? half_thicknessY : -half_thicknessY; } else { distanceX = half_thicknessX / Math.sin(half_angle); distanceY = half_thicknessY / Math.sin(half_angle); } /*console.log("\ndir_delta", dir_delta); console.log("half_angle", half_angle); console.log("distance", distance); console.log("dist", dist);*/ /* var distx:number=end_x-prev_x; var disty:number=end_y-prev_y; var dist:number=Math.sqrt(distx*distx + disty*disty); distx=prev_prev_x-prev_x; disty=prev_prev_y-prev_y; var dist2:number=Math.sqrt(distx*distx + disty*disty); if(dist2dist){ //distance=(distance>=0)?dist:-dist; }*/ // get the merged points left and right // by moving from original point along the direction vector right_point_merged_x = prev_x - (tmp_point2.x * distanceX); right_point_merged_y = prev_y - (tmp_point2.y * distanceY); left_point_merged_x = prev_x + (tmp_point2.x * distanceX); left_point_merged_y = prev_y + (tmp_point2.y * distanceY); // use different points dependent if dir_delta is positive or negative if (dir_delta > 0) { left_point_contour_x = prev_x - (tmp_point.x * half_thicknessX); left_point_contour_y = prev_y - (tmp_point.y * half_thicknessY); left_point_contour_prev_x = prev_x - (prev_normal_x * half_thicknessX); left_point_contour_prev_y = prev_y - (prev_normal_y * half_thicknessY); right_point_x = right_point_merged_x; right_point_y = right_point_merged_y; left_point_x = left_point_contour_x; left_point_y = left_point_contour_y; } else { right_point_contour_x = prev_x + (tmp_point.x * half_thicknessX); right_point_contour_y = prev_y + (tmp_point.y * half_thicknessY); right_point_contour_prev_x = prev_x + (prev_normal_x * half_thicknessX); right_point_contour_prev_y = prev_y + (prev_normal_y * half_thicknessY); left_point_x = left_point_merged_x; left_point_y = left_point_merged_y; right_point_x = right_point_contour_x; right_point_y = right_point_contour_y; } addJoints = true; // check if we need to add a joint if (strokeStyle.jointstyle == JointStyle.MITER) { // miter means that we have no bevel effect on the corners, // as long as the mitter-value is within a given miter_limit // eslint-disable-next-line max-len distance_miter = Math.sqrt((distanceX * distanceX + distanceY * distanceY) / (half_thicknessX * half_thicknessX + half_thicknessY * half_thicknessY) - 1); if (distance_miter <= strokeStyle.miterLimit) { // if within miter_limit, miter is applied, // and we only need to add the merged points for both sides addJoints = false; left_point_x = left_point_merged_x; left_point_y = left_point_merged_y; right_point_x = right_point_merged_x; right_point_y = right_point_merged_y; } else { if (dir_delta > 0) { // right side is merged, left side has 2 points // eslint-disable-next-line max-len left_point_contour_x = left_point_contour_x - (tmp_dir_point.x * (strokeStyle.miterLimit * half_thicknessX)); // eslint-disable-next-line max-len left_point_contour_y = left_point_contour_y - (tmp_dir_point.y * (strokeStyle.miterLimit * half_thicknessY)); tmp_point3.x = prev_normal_y * -1; tmp_point3.y = prev_normal_x; // eslint-disable-next-line max-len left_point_contour_prev_x = left_point_contour_prev_x - (tmp_point3.x * (strokeStyle.miterLimit * half_thicknessX)); // eslint-disable-next-line max-len left_point_contour_prev_y = left_point_contour_prev_y - (tmp_point3.y * (strokeStyle.miterLimit * half_thicknessY)); } else { // left side is merged, right side has 2 points // eslint-disable-next-line max-len right_point_contour_x = right_point_contour_x - (tmp_dir_point.x * (strokeStyle.miterLimit * half_thicknessX)); // eslint-disable-next-line max-len right_point_contour_y = right_point_contour_y - (tmp_dir_point.y * (strokeStyle.miterLimit * half_thicknessY)); tmp_point3.x = prev_normal_y * -1; tmp_point3.y = prev_normal_x; // eslint-disable-next-line max-len right_point_contour_prev_x = right_point_contour_prev_x - (tmp_point3.x * (strokeStyle.miterLimit * half_thicknessX)); // eslint-disable-next-line max-len right_point_contour_prev_y = right_point_contour_prev_y - (tmp_point3.y * (strokeStyle.miterLimit * half_thicknessY)); } } } if (addJoints) { new_cmds[new_cmds_cnt++] = (strokeStyle.jointstyle != JointStyle.ROUND) ? GraphicsPathCommand.BUILD_JOINT : GraphicsPathCommand.BUILD_ROUND_JOINT; if (dir_delta > 0) { // right side is merged, left side has 2 points new_pnts[new_pnts_cnt++] = right_point_merged_x; new_pnts[new_pnts_cnt++] = right_point_merged_y; new_pnts[new_pnts_cnt++] = left_point_contour_prev_x; new_pnts[new_pnts_cnt++] = left_point_contour_prev_y; new_pnts[new_pnts_cnt++] = left_point_contour_x; new_pnts[new_pnts_cnt++] = left_point_contour_y; } else { // left side is merged, right side has 2 points new_pnts[new_pnts_cnt++] = right_point_contour_prev_x; new_pnts[new_pnts_cnt++] = right_point_contour_prev_y; new_pnts[new_pnts_cnt++] = left_point_merged_x; new_pnts[new_pnts_cnt++] = left_point_merged_y; new_pnts[new_pnts_cnt++] = right_point_contour_x; new_pnts[new_pnts_cnt++] = right_point_contour_y; } if (strokeStyle.jointstyle == JointStyle.ROUND) { new_pnts[new_pnts_cnt++] = prev_x - (tmp_point2.x * Math.abs(half_thicknessX)); new_pnts[new_pnts_cnt++] = prev_y - (tmp_point2.y * Math.abs(half_thicknessY)); if (dir_delta > 0) { new_pnts[new_pnts_cnt++] = left_point_contour_prev_x; new_pnts[new_pnts_cnt++] = left_point_contour_prev_y; new_pnts[new_pnts_cnt++] = left_point_contour_x; new_pnts[new_pnts_cnt++] = left_point_contour_y; } else { new_pnts[new_pnts_cnt++] = right_point_contour_prev_x; new_pnts[new_pnts_cnt++] = right_point_contour_prev_y; new_pnts[new_pnts_cnt++] = right_point_contour_x; new_pnts[new_pnts_cnt++] = right_point_contour_y; } } } } } prev_normal_x = tmp_point.x; prev_normal_y = tmp_point.y; if (add_segment) { new_cmds[new_cmds_cnt++] = GraphicsPathCommand.LINE_TO; new_pnts[new_pnts_cnt++] = right_point_x; new_pnts[new_pnts_cnt++] = right_point_y; new_pnts[new_pnts_cnt++] = left_point_x; new_pnts[new_pnts_cnt++] = left_point_y; } prev_x = end_x; prev_y = end_y; if (i == data.length - 2) { new_cmds[new_cmds_cnt++] = GraphicsPathCommand.NO_OP; if (!closed) { new_pnts[new_pnts_cnt++] = prev_x + (tmp_point.x * half_thicknessX); new_pnts[new_pnts_cnt++] = prev_y + (tmp_point.y * half_thicknessY); new_pnts[new_pnts_cnt++] = prev_x - (tmp_point.x * half_thicknessX); new_pnts[new_pnts_cnt++] = prev_y - (tmp_point.y * half_thicknessY); } else { new_pnts[new_pnts_cnt++] = new_pnts[0]; new_pnts[new_pnts_cnt++] = new_pnts[1]; new_pnts[new_pnts_cnt++] = new_pnts[2]; new_pnts[new_pnts_cnt++] = new_pnts[3]; } } } } new_cmds_cnt = 0; new_pnts_cnt = 0; for (i = 0; i < new_cmds.length; i++) { if (new_cmds[i] == GraphicsPathCommand.LINE_TO) { start_right_x = new_pnts[new_pnts_cnt++]; start_right_y = new_pnts[new_pnts_cnt++]; start_left_x = new_pnts[new_pnts_cnt++]; start_left_y = new_pnts[new_pnts_cnt++]; end_right_x = new_pnts[new_pnts_cnt]; end_right_y = new_pnts[new_pnts_cnt + 1]; end_left_x = new_pnts[new_pnts_cnt + 2]; end_left_y = new_pnts[new_pnts_cnt + 3]; //0GraphicsFactoryHelper.drawPoint(start_right.x,start_right.y, final_vert_list, false); //GraphicsFactoryHelper.drawPoint(start_left.x,start_left.y, final_vert_list, false); //GraphicsFactoryHelper.drawPoint(end_right.x,end_right.y, final_vert_list, false); //GraphicsFactoryHelper.drawPoint(end_left_x,end_left_y, final_vert_list, false); GraphicsFactoryHelper.addTriangle( start_right_x, start_right_y, end_right_x, end_right_y, start_left_x, start_left_y, 0, final_vert_list, curves); GraphicsFactoryHelper.addTriangle( start_left_x, start_left_y, end_right_x, end_right_y, end_left_x, end_left_y, 0, final_vert_list, curves); } else if (new_cmds[i] >= GraphicsPathCommand.BUILD_JOINT) { end_right_x = new_pnts[new_pnts_cnt++]; end_right_y = new_pnts[new_pnts_cnt++]; start_right_x = new_pnts[new_pnts_cnt++]; start_right_y = new_pnts[new_pnts_cnt++]; start_left_x = new_pnts[new_pnts_cnt++]; start_left_y = new_pnts[new_pnts_cnt++]; GraphicsFactoryHelper.addTriangle( start_right_x, start_right_y, start_left_x, start_left_y, end_right_x, end_right_y, 0, final_vert_list, curves); if (new_cmds[i] == GraphicsPathCommand.BUILD_ROUND_JOINT) { end_left_x = new_pnts[new_pnts_cnt++]; end_left_y = new_pnts[new_pnts_cnt++]; start_right_x = new_pnts[new_pnts_cnt++]; start_right_y = new_pnts[new_pnts_cnt++]; start_left_x = new_pnts[new_pnts_cnt++]; start_left_y = new_pnts[new_pnts_cnt++]; GraphicsFactoryHelper.tesselateCurve( start_right_x, start_right_y, end_left_x, end_left_y, start_left_x, start_left_y, final_vert_list, true); } } } if (!closed) { last_dir_vec.x = data[2] - data[0]; last_dir_vec.y = data[3] - data[1]; last_dir_vec.normalize(); GraphicsFactoryHelper.createCap( data[0], data[1], new_pnts[0], new_pnts[1], new_pnts[2], new_pnts[3], last_dir_vec.x, last_dir_vec.y, strokeStyle.capstyle, -1, half_thicknessX, half_thicknessY, final_vert_list, curves); last_dir_vec.x = data[data.length - 2] - data[data.length - 4]; last_dir_vec.y = data[data.length - 1] - data[data.length - 3]; last_dir_vec.normalize(); GraphicsFactoryHelper.createCap( data[data.length - 2], data[data.length - 1], new_pnts[new_pnts.length - 4], new_pnts[new_pnts.length - 3], new_pnts[new_pnts.length - 2], new_pnts[new_pnts.length - 1], last_dir_vec.x, last_dir_vec.y, strokeStyle.capstyle, 1, half_thicknessX, half_thicknessY, final_vert_list, curves); } } } //targetGraphic.queued_stroke_pathes.length=0; if (final_vert_list.length == 0) return; const attributesView: AttributesView = new AttributesView(Float32Array, curves ? 3 : 2); attributesView.set(final_vert_list); const attributesBuffer: AttributesBuffer = attributesView.attributesBuffer.cloneBufferView(); attributesView.dispose(); const elements: TriangleElements = new TriangleElements(attributesBuffer); elements.setPositions(new Float2Attributes(attributesBuffer)); return elements; } }