/* * JaamSim Discrete Event Simulation * Copyright (C) 2015 Ausenco Engineering Canada Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.jaamsim.Graphics; import java.util.ArrayList; import com.jaamsim.math.Color4d; import com.jaamsim.math.Vec3d; public class PolylineInfo { public static enum CurveType { LINEAR, BEZIER, SPLINE, } private final ArrayList<Vec3d> points; private final CurveType curveType; private final ArrayList<Vec3d> curvePoints; private final Color4d color; private final int width; // Line width in pixels public PolylineInfo(ArrayList<Vec3d> pts, CurveType ct, Color4d col, int w) { points = pts; curveType = ct; color = col; width = w; switch (curveType) { case LINEAR: curvePoints = points; break; case BEZIER: curvePoints = getBezierPoints(points); break; case SPLINE: curvePoints = getSplinePoints(points); break; default: assert(false); curvePoints = null; } } @Override public boolean equals(Object o) { if (o == this) return true; if (!(o instanceof PolylineInfo)) return false; PolylineInfo pi = (PolylineInfo)o; return points != null && points.equals(pi.points) && curveType == pi.curveType && color != null && color.equals(pi.color) && width == pi.width; } public ArrayList<Vec3d> getPoints() { return points; } public ArrayList<Vec3d> getCurvePoints() { return curvePoints; } public Color4d getColor() { return color; } public int getWidth() { return width; } @Override public String toString() { return points.toString(); } public static ArrayList<Vec3d> getBezierPoints(ArrayList<Vec3d> ps) { ArrayList<Vec3d> ret = new ArrayList<>(); // The total number of segments in this curve final int NUM_POINTS = 32; double tInc = 1.0/NUM_POINTS; for (int i = 0; i < NUM_POINTS; ++i) { ret.add(solveBezier(i*tInc, 0, ps.size()-1, ps)); } ret.add(ps.get(ps.size()-1)); return ret; } public static ArrayList<Vec3d> getSplinePoints(ArrayList<Vec3d> ps) { // This spline fitting algorithm is a bit of a custom creation. It is loosely based on the finte differences method described // in this article: https://en.wikipedia.org/wiki/Cubic_Hermite_spline (assuming it hasn't changed since). // The major changes are: // * All segments are solved in Bezier form (this is more stylist than a change in algorithm) // * The end two segments are quadratic Bezier curves (not cubic) and as such the end tangent is un-constrained. // * The internal control points are scaled down proportional to segment length to avoid kinks and self intersection within a segment // If there's too few points, just return the existing line if (ps.size() <= 2) { return ps; } // The number of segments between each control point final int NUM_SEGMENTS = 16; Vec3d temp = new Vec3d(); // Generate tangents for internal points only ArrayList<Vec3d> tangents = new ArrayList<>(); for (int i = 1; i < ps.size()-1; ++i) { Vec3d pMin = ps.get(i-1); Vec3d p = ps.get(i); Vec3d pPlus = ps.get(i+1); temp.sub3(p, pMin); double l0 = temp.mag3(); temp.sub3(p, pPlus); double l1 = temp.mag3(); Vec3d tan = new Vec3d(); tan.sub3(pPlus, pMin); tan.scale3(1.0/(l0 + l1)); tangents.add(tan); } double tInc = 1.0/NUM_SEGMENTS; ArrayList<Vec3d> ret = new ArrayList<>(); Vec3d scaledTanTemp = new Vec3d(); Vec3d segDiffTemp = new Vec3d(); // Start with a quadratic segment { Vec3d p0 = ps.get(0); Vec3d p1 = ps.get(1); segDiffTemp.sub3(p0, p1); double segLength = segDiffTemp.mag3(); Vec3d c = new Vec3d(p1); scaledTanTemp.scale3(segLength/2.0, tangents.get(0)); c.sub3(scaledTanTemp); for (int t = 0; t < NUM_SEGMENTS; ++t) { Vec3d curvePoint = solveQuadraticBezier(t*tInc, p0, p1, c); ret.add(curvePoint); } } // Internal segments are cubic for (int i = 2; i < ps.size()-1; ++i) { Vec3d p0 = ps.get(i-1); Vec3d p1 = ps.get(i); segDiffTemp.sub3(p0, p1); double segLength = segDiffTemp.mag3(); Vec3d c0 = new Vec3d(p0); scaledTanTemp.scale3(segLength/3.0, tangents.get(i-2)); c0.add3(scaledTanTemp); Vec3d c1 = new Vec3d(p1); scaledTanTemp.scale3(segLength/3.0, tangents.get(i-1)); c1.sub3(scaledTanTemp); for (int t = 0; t < NUM_SEGMENTS; ++t) { Vec3d curvePoint = solveCubicBezier(t*tInc, p0, p1, c0, c1); ret.add(curvePoint); } } // End with another quadratic segment { Vec3d p0 = ps.get(ps.size()-2); Vec3d p1 = ps.get(ps.size()-1); segDiffTemp.sub3(p0, p1); double segLength = segDiffTemp.mag3(); Vec3d c = new Vec3d(p0); scaledTanTemp.scale3(segLength/2.0, tangents.get(tangents.size()-1)); c.add3(scaledTanTemp); for (int t = 0; t < NUM_SEGMENTS; ++t) { Vec3d curvePoint = solveQuadraticBezier(t*tInc, p0, p1, c); ret.add(curvePoint); } } ret.add(ps.get(ps.size()-1)); return ret; } // Solve a generic bezier with arbitrary control points private static Vec3d solveBezier(double t, int start, int end, ArrayList<Vec3d> controls) { // Termination case if (start == end) { return new Vec3d(controls.get(start)); } Vec3d a = solveBezier(t, start, end-1, controls); Vec3d b = solveBezier(t, start+1, end, controls); a.scale3(1-t); b.scale3(t); a.add3(b); return a; } // Solve a cubic bezier with explicit control points private static Vec3d solveCubicBezier(double s, Vec3d p0, Vec3d p1, Vec3d c0, Vec3d c1) { double oneMinS = 1 - s; double coeffP0 = oneMinS*oneMinS*oneMinS; double coeffC0 = 3*s*oneMinS*oneMinS; double coeffC1 = 3*s*s*oneMinS; double coeffP1 = s*s*s; Vec3d lp0 = new Vec3d(p0); lp0.scale3(coeffP0); Vec3d lp1 = new Vec3d(p1); lp1.scale3(coeffP1); Vec3d lc0 = new Vec3d(c0); lc0.scale3(coeffC0); Vec3d lc1 = new Vec3d(c1); lc1.scale3(coeffC1); Vec3d ret = new Vec3d(); ret.add3(lp0); ret.add3(lp1); ret.add3(lc0); ret.add3(lc1); return ret; } // Solve a quadratic bezier with an explicit control point private static Vec3d solveQuadraticBezier(double s, Vec3d p0, Vec3d p1, Vec3d c) { double oneMinS = 1 - s; double coeffP0 = oneMinS*oneMinS; double coeffC = 2*s*oneMinS; double coeffP1 = s*s; Vec3d lp0 = new Vec3d(p0); lp0.scale3(coeffP0); Vec3d lp1 = new Vec3d(p1); lp1.scale3(coeffP1); Vec3d lc = new Vec3d(c); lc.scale3(coeffC); Vec3d ret = new Vec3d(); ret.add3(lp0); ret.add3(lp1); ret.add3(lc); return ret; } }