mirror of
https://github.com/bancika/diy-layout-creator
synced 2026-11-07 12:24:58 +10:00
Added Box style multi-turn potentiometer component. (#987)
* Added Box style multi-turn potentiometer component. Implements box style multi-turn trimmer potentiometer (e.g. Bourns PV36W/X) with inline pins for stripboard layouts. Features: - 3 inline pins on 0.1" spacing (CCW, Wiper, CW) - Configurable resistance value and orientation - Blue body (4.82mm × 9.53mm) with brass adjustment screw - Screw positioned in lower left corner with horizontal slot - Label positioned outside body for readability - Pins centered in body width for all orientations - Side profile icon showing brass screw and three pins - Locked pin movement - component moves as single unit Dimensions based on common box-style multi-turn potentiometers. Default orientation is vertical (_90) with pins horizontal. Category: Passive Instance prefix: VR * Handles mirroring and rotational transforms. * Handles mirroring and rotational transforms.
This commit is contained in:
@@ -0,0 +1,393 @@
|
||||
/*
|
||||
|
||||
DIY Layout Creator (DIYLC).
|
||||
Copyright (c) 2009-2025 held jointly by the individual authors.
|
||||
|
||||
This file is part of DIYLC.
|
||||
|
||||
DIYLC is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
DIYLC is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with DIYLC. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
*/
|
||||
package org.diylc.components.passive;
|
||||
|
||||
import java.awt.AlphaComposite;
|
||||
import java.awt.Color;
|
||||
import java.awt.Composite;
|
||||
import java.awt.Graphics2D;
|
||||
import java.awt.geom.Point2D;
|
||||
import java.io.Serial;
|
||||
|
||||
import org.diylc.appframework.miscutils.ConfigurationManager;
|
||||
import org.diylc.common.Display;
|
||||
import org.diylc.common.IPlugInPort;
|
||||
import org.diylc.common.ObjectCache;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.AbstractTransparentComponent;
|
||||
import org.diylc.core.ComponentState;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
import org.diylc.core.IDrawingObserver;
|
||||
import org.diylc.core.Project;
|
||||
import org.diylc.core.Theme;
|
||||
import org.diylc.core.VisibilityPolicy;
|
||||
import org.diylc.core.annotations.ComponentDescriptor;
|
||||
import org.diylc.core.annotations.EditableProperty;
|
||||
import org.diylc.core.measures.Resistance;
|
||||
import org.diylc.core.measures.ResistanceUnit;
|
||||
import org.diylc.core.measures.Size;
|
||||
import org.diylc.core.measures.SizeUnit;
|
||||
import org.diylc.utils.Constants;
|
||||
|
||||
@ComponentDescriptor(name = "Box Multi-Turn Pot", author = "Clive", category = "Passive",
|
||||
instanceNamePrefix = "VR", description = "Box style multi-turn potentiometer (e.g. Bourns PV36)",
|
||||
zOrder = IDIYComponent.COMPONENT, transformer = org.diylc.components.transform.BoxTrimmerTransformer.class)
|
||||
public class BoxTrimmer extends AbstractTransparentComponent<Resistance> {
|
||||
|
||||
@Serial
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public static Size PIN_SPACING = new Size(0.1d, SizeUnit.in);
|
||||
public static Size BODY_WIDTH = new Size(0.19d, SizeUnit.in); // 2x 2.41mm = 4.82mm ≈ 0.19"
|
||||
public static Size BODY_LENGTH = new Size(0.375d, SizeUnit.in); // 9.53mm from datasheet
|
||||
public static Color BODY_COLOR = Color.decode("#4477BB");
|
||||
public static Color BORDER_COLOR = BODY_COLOR.darker();
|
||||
public static Color PIN_COLOR = Color.decode("#CCCCCC");
|
||||
|
||||
private Resistance value = new Resistance(10d, ResistanceUnit.K);
|
||||
private Orientation orientation = Orientation._90; // Default to vertical (90 degrees)
|
||||
private Display display = Display.NAME;
|
||||
private Color bodyColor = BODY_COLOR;
|
||||
private Color borderColor = BORDER_COLOR;
|
||||
protected String name;
|
||||
private boolean screwFlippedHorizontally = false; // Track if screw flipped left-right
|
||||
private boolean screwFlippedVertically = false; // Track if screw flipped top-bottom
|
||||
|
||||
private Point2D[] controlPoints = new Point2D[3];
|
||||
|
||||
public BoxTrimmer() {
|
||||
super();
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void draw(Graphics2D g2d, ComponentState componentState, boolean outlineMode,
|
||||
Project project, IDrawingObserver drawingObserver) {
|
||||
|
||||
Theme theme = (Theme) ConfigurationManager.getInstance().readObject(
|
||||
IPlugInPort.THEME_KEY, Constants.DEFAULT_THEME);
|
||||
|
||||
int pinSpacing = (int) PIN_SPACING.convertToPixels();
|
||||
int bodyWidth = (int) BODY_WIDTH.convertToPixels();
|
||||
int bodyLength = (int) BODY_LENGTH.convertToPixels();
|
||||
|
||||
// Calculate positions - body is centered on the middle pin
|
||||
int centerX = (int) controlPoints[1].getX();
|
||||
int centerY = (int) controlPoints[1].getY();
|
||||
|
||||
int bodyX, bodyY, bodyW, bodyH;
|
||||
|
||||
// Position body based on orientation - fits tightly around pins
|
||||
if (orientation == Orientation.DEFAULT || orientation == Orientation._180) {
|
||||
// Horizontal - body is wide, pins are below/above
|
||||
bodyW = bodyLength;
|
||||
bodyH = bodyWidth;
|
||||
bodyX = centerX - bodyLength / 2;
|
||||
bodyY = centerY - bodyWidth / 2;
|
||||
} else {
|
||||
// Vertical - body is tall, pins are left/right
|
||||
bodyW = bodyWidth;
|
||||
bodyH = bodyLength;
|
||||
bodyX = centerX - bodyWidth / 2;
|
||||
bodyY = centerY - bodyLength / 2;
|
||||
}
|
||||
|
||||
// Draw body
|
||||
Composite oldComposite = g2d.getComposite();
|
||||
if (alpha < MAX_ALPHA) {
|
||||
g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER,
|
||||
1f * alpha / MAX_ALPHA));
|
||||
}
|
||||
g2d.setColor(outlineMode ? Constants.TRANSPARENT_COLOR : bodyColor);
|
||||
g2d.fillRoundRect(bodyX, bodyY, bodyW, bodyH, bodyW / 10, bodyH / 10);
|
||||
g2d.setComposite(oldComposite);
|
||||
|
||||
// Draw border
|
||||
Color finalBorderColor = componentState == ComponentState.SELECTED ||
|
||||
componentState == ComponentState.DRAGGING ? SELECTION_COLOR :
|
||||
(outlineMode ? theme.getOutlineColor() : borderColor);
|
||||
g2d.setColor(finalBorderColor);
|
||||
g2d.setStroke(ObjectCache.getInstance().fetchBasicStroke(1));
|
||||
g2d.drawRoundRect(bodyX, bodyY, bodyW, bodyH, bodyW / 10, bodyH / 10);
|
||||
|
||||
// Draw adjustment screw (slotted screw head) - position based on orientation
|
||||
if (!outlineMode) {
|
||||
// Brass/gold color for screw head
|
||||
Color brassColor = new Color(184, 134, 11); // Dark goldenrod
|
||||
|
||||
int screwDiameter = Math.min(bodyW, bodyH) / 3;
|
||||
int screwX, screwY;
|
||||
|
||||
// Position screw based on orientation and flip state
|
||||
// The screw should be in the corner near pin 0, but can flip horizontally or vertically
|
||||
screwY = switch (orientation) {
|
||||
case DEFAULT -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX + bodyW - screwDiameter : bodyX;
|
||||
yield screwFlippedVertically ? bodyY : bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
case _90 -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX + bodyW - screwDiameter : bodyX;
|
||||
yield screwFlippedVertically ? bodyY : bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
case _180 -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX : bodyX + bodyW - screwDiameter;
|
||||
yield screwFlippedVertically ? bodyY : bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
case _270 -> {
|
||||
screwX = screwFlippedHorizontally ? bodyX + bodyW - screwDiameter : bodyX;
|
||||
yield screwFlippedVertically ? bodyY + bodyH - screwDiameter : bodyY;
|
||||
}
|
||||
default -> {
|
||||
screwX = bodyX;
|
||||
yield bodyY + bodyH - screwDiameter;
|
||||
}
|
||||
};
|
||||
|
||||
// Draw screw head circle
|
||||
g2d.setColor(brassColor);
|
||||
g2d.fillOval(screwX, screwY, screwDiameter, screwDiameter);
|
||||
|
||||
// Draw slot in screw - centered horizontally through the circle
|
||||
g2d.setColor(new Color(80, 80, 80)); // Dark gray for slot
|
||||
int slotLength = (int)(screwDiameter * 0.7);
|
||||
int slotThickness = Math.max(1, screwDiameter / 10);
|
||||
int slotCenterX = screwX + screwDiameter / 2;
|
||||
int slotCenterY = screwY + screwDiameter / 2;
|
||||
g2d.fillRect(slotCenterX - slotLength / 2, slotCenterY - slotThickness / 2, slotLength, slotThickness);
|
||||
}
|
||||
|
||||
// Draw pins
|
||||
g2d.setColor(outlineMode ? theme.getOutlineColor() : PIN_COLOR);
|
||||
for (Point2D p : controlPoints) {
|
||||
g2d.fillOval((int)p.getX() - 2, (int)p.getY() - 2, 4, 4);
|
||||
}
|
||||
|
||||
// Draw label - position outside body for readability
|
||||
Color labelColor = componentState == ComponentState.SELECTED ||
|
||||
componentState == ComponentState.DRAGGING ? LABEL_COLOR_SELECTED : LABEL_COLOR;
|
||||
g2d.setColor(labelColor);
|
||||
g2d.setFont(project.getFont());
|
||||
|
||||
String label = display == Display.NAME ? getName() :
|
||||
display == Display.VALUE ? getValue().toString() :
|
||||
display == Display.BOTH ? getName() + " " + getValue() : "";
|
||||
|
||||
if (!label.isEmpty()) {
|
||||
// Position label outside the component body based on orientation
|
||||
int labelX, labelY;
|
||||
java.awt.FontMetrics fm = g2d.getFontMetrics();
|
||||
|
||||
if (orientation == Orientation.DEFAULT || orientation == Orientation._180) {
|
||||
// Horizontal orientation - label below body
|
||||
labelX = bodyX + bodyW / 2;
|
||||
labelY = bodyY + bodyH + fm.getHeight();
|
||||
} else {
|
||||
// Vertical orientation - label to the right of body
|
||||
labelX = bodyX + bodyW + fm.stringWidth(label) / 2 + 5; // 5 pixel gap
|
||||
labelY = bodyY + bodyH / 2;
|
||||
}
|
||||
|
||||
drawCenteredText(g2d, label, labelX, labelY);
|
||||
}
|
||||
}
|
||||
|
||||
private void drawCenteredText(Graphics2D g2d, String text, int x, int y) {
|
||||
java.awt.FontMetrics fm = g2d.getFontMetrics();
|
||||
int w = fm.stringWidth(text);
|
||||
int h = fm.getHeight();
|
||||
g2d.drawString(text, x - w / 2, y + fm.getAscent() - h / 2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void drawIcon(Graphics2D g2d, int width, int height) {
|
||||
// Side profile view of PV36 trimmer
|
||||
int bodyWidth = width - 8;
|
||||
int bodyHeight = height - 10;
|
||||
int bodyX = 4;
|
||||
int bodyY = 2;
|
||||
|
||||
// Draw blue body (side profile - rectangular)
|
||||
g2d.setColor(BODY_COLOR);
|
||||
g2d.fillRect(bodyX, bodyY, bodyWidth, bodyHeight);
|
||||
g2d.setColor(BORDER_COLOR);
|
||||
g2d.drawRect(bodyX, bodyY, bodyWidth, bodyHeight);
|
||||
|
||||
// Draw brass adjustment screw on top left
|
||||
Color brassColor = new Color(184, 134, 11);
|
||||
int screwSize = Math.min(bodyWidth, bodyHeight) / 3;
|
||||
g2d.setColor(brassColor);
|
||||
g2d.fillOval(bodyX + 2, bodyY - screwSize / 2, screwSize, screwSize);
|
||||
|
||||
// Draw slot in screw
|
||||
g2d.setColor(new Color(80, 80, 80));
|
||||
int slotLen = (int)(screwSize * 0.6);
|
||||
g2d.drawLine(bodyX + 2 + screwSize / 2 - slotLen / 2, bodyY,
|
||||
bodyX + 2 + screwSize / 2 + slotLen / 2, bodyY);
|
||||
|
||||
// Draw three pins at bottom (light gray)
|
||||
g2d.setColor(PIN_COLOR);
|
||||
int pinSpacing = bodyWidth / 4;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
int pinX = bodyX + pinSpacing * (i + 1) - 1;
|
||||
int pinY = bodyY + bodyHeight;
|
||||
g2d.fillRect(pinX, pinY, 2, 6);
|
||||
}
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Resistance getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(Resistance value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Orientation getOrientation() {
|
||||
return orientation;
|
||||
}
|
||||
|
||||
public void setOrientation(Orientation orientation) {
|
||||
this.orientation = orientation;
|
||||
updateControlPoints();
|
||||
}
|
||||
|
||||
public boolean getScrewFlippedHorizontally() {
|
||||
return screwFlippedHorizontally;
|
||||
}
|
||||
|
||||
public void setScrewFlippedHorizontally(boolean flipped) {
|
||||
this.screwFlippedHorizontally = flipped;
|
||||
}
|
||||
|
||||
public boolean getScrewFlippedVertically() {
|
||||
return screwFlippedVertically;
|
||||
}
|
||||
|
||||
public void setScrewFlippedVertically(boolean flipped) {
|
||||
this.screwFlippedVertically = flipped;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Display getDisplay() {
|
||||
return display;
|
||||
}
|
||||
|
||||
public void setDisplay(Display display) {
|
||||
this.display = display;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Color getBodyColor() {
|
||||
return bodyColor;
|
||||
}
|
||||
|
||||
public void setBodyColor(Color color) {
|
||||
this.bodyColor = color;
|
||||
}
|
||||
|
||||
@EditableProperty
|
||||
public Color getBorderColor() {
|
||||
return borderColor;
|
||||
}
|
||||
|
||||
public void setBorderColor(Color color) {
|
||||
this.borderColor = color;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getControlPointCount() {
|
||||
return 3;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Point2D getControlPoint(int index) {
|
||||
return controlPoints[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public VisibilityPolicy getControlPointVisibilityPolicy(int index) {
|
||||
return VisibilityPolicy.WHEN_SELECTED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setControlPoint(Point2D point, int index) {
|
||||
// Only allow moving via the first control point
|
||||
if (index == 0) {
|
||||
controlPoints[0] = new Point2D.Double(point.getX(), point.getY());
|
||||
updateControlPoints();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateControlPoints() {
|
||||
int pinSpacing = (int) PIN_SPACING.convertToPixels();
|
||||
double x = controlPoints[0] != null ? controlPoints[0].getX() : 0;
|
||||
double y = controlPoints[0] != null ? controlPoints[0].getY() : 0;
|
||||
|
||||
controlPoints[0] = new Point2D.Double(x, y);
|
||||
|
||||
// Pin arrangement depends on orientation
|
||||
// Pin 0 is always CCW, Pin 1 is Wiper, Pin 2 is CW
|
||||
switch (orientation) {
|
||||
case DEFAULT: // Horizontal, pins go right from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x + pinSpacing, y);
|
||||
controlPoints[2] = new Point2D.Double(x + 2 * pinSpacing, y);
|
||||
break;
|
||||
case _90: // Vertical, pins go down from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x, y + pinSpacing);
|
||||
controlPoints[2] = new Point2D.Double(x, y + 2 * pinSpacing);
|
||||
break;
|
||||
case _180: // Horizontal mirrored, pins go left from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x - pinSpacing, y);
|
||||
controlPoints[2] = new Point2D.Double(x - 2 * pinSpacing, y);
|
||||
break;
|
||||
case _270: // Vertical mirrored, pins go up from pin 0
|
||||
controlPoints[1] = new Point2D.Double(x, y - pinSpacing);
|
||||
controlPoints[2] = new Point2D.Double(x, y - 2 * pinSpacing);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getControlPointNodeName(int index) {
|
||||
return new String[] {"CCW (1)", "Wiper (2)", "CW (3)"}[index];
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return name != null && !name.trim().isEmpty() ? name : super.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
@EditableProperty(defaultable = false)
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isControlPointSticky(int index) {
|
||||
// First control point is sticky so component can be placed from side panel
|
||||
// But setControlPoint ensures all pins move together
|
||||
return index == 0;
|
||||
}
|
||||
}
|
||||
+234
@@ -0,0 +1,234 @@
|
||||
package org.diylc.components.transform;
|
||||
|
||||
import java.awt.geom.AffineTransform;
|
||||
import java.awt.geom.Point2D;
|
||||
|
||||
import org.diylc.common.IComponentTransformer;
|
||||
import org.diylc.common.Orientation;
|
||||
import org.diylc.components.passive.BoxTrimmer;
|
||||
import org.diylc.core.IDIYComponent;
|
||||
|
||||
public class BoxTrimmerTransformer implements IComponentTransformer {
|
||||
|
||||
private enum Corner { TL, TR, BL, BR }
|
||||
|
||||
// ---------- public API ----------
|
||||
|
||||
@Override
|
||||
public boolean mirroringChangesCircuit() {
|
||||
// Pins are by index (0=CCW,1=Wiper,2=CW); mirror doesn't swap indices.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canMirror(IDIYComponent<?> component) {
|
||||
return component instanceof BoxTrimmer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRotate(IDIYComponent<?> component) {
|
||||
return component instanceof BoxTrimmer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rotate(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
if (!(component instanceof BoxTrimmer t)) return;
|
||||
if (direction != 1 && direction != -1) return;
|
||||
|
||||
// capture old state
|
||||
Orientation oldO = t.getOrientation();
|
||||
Corner oldCorner = flagsToCorner(oldO, t.getScrewFlippedHorizontally(), t.getScrewFlippedVertically());
|
||||
|
||||
// rotate CP0 about center (clockwise for direction=+1)
|
||||
Point2D p0 = t.getControlPoint(0);
|
||||
AffineTransform rot = AffineTransform.getRotateInstance((Math.PI / 2) * -direction, center.getX(), center.getY());
|
||||
Point2D p0r = new Point2D.Double();
|
||||
rot.transform(p0, p0r);
|
||||
|
||||
// compute new orientation and new corner
|
||||
Orientation newO = rotateOrientation(oldO, direction);
|
||||
Corner newCorner = (direction == 1) ? rotateCornerCW(oldCorner) : rotateCornerCCW(oldCorner);
|
||||
|
||||
// decode corner back to flags in new orientation
|
||||
boolean[] hv = cornerToFlags(newO, newCorner);
|
||||
|
||||
// apply (CP0 first, then orientation regenerates pins)
|
||||
t.setControlPoint(p0r, 0);
|
||||
t.setOrientation(newO);
|
||||
t.setScrewFlippedHorizontally(hv[0]);
|
||||
t.setScrewFlippedVertically(hv[1]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void mirror(IDIYComponent<?> component, Point2D center, int direction) {
|
||||
if (!(component instanceof BoxTrimmer t)) return;
|
||||
|
||||
Orientation oldO = t.getOrientation();
|
||||
Corner oldCorner = flagsToCorner(oldO, t.getScrewFlippedHorizontally(), t.getScrewFlippedVertically());
|
||||
|
||||
Point2D p0 = t.getControlPoint(0);
|
||||
Point2D p0m;
|
||||
|
||||
Orientation newO;
|
||||
Corner newCorner;
|
||||
|
||||
if (direction == IComponentTransformer.HORIZONTAL) {
|
||||
// mirror left-right
|
||||
p0m = new Point2D.Double(2 * center.getX() - p0.getX(), p0.getY());
|
||||
newO = mirrorOrientationHorizontal(oldO);
|
||||
newCorner = mirrorCornerHorizontal(oldCorner);
|
||||
} else {
|
||||
// mirror top-bottom
|
||||
p0m = new Point2D.Double(p0.getX(), 2 * center.getY() - p0.getY());
|
||||
newO = mirrorOrientationVertical(oldO);
|
||||
newCorner = mirrorCornerVertical(oldCorner);
|
||||
}
|
||||
|
||||
boolean[] hv = cornerToFlags(newO, newCorner);
|
||||
|
||||
t.setControlPoint(p0m, 0);
|
||||
t.setOrientation(newO);
|
||||
t.setScrewFlippedHorizontally(hv[0]);
|
||||
t.setScrewFlippedVertically(hv[1]);
|
||||
}
|
||||
|
||||
// ---------- orientation index + transforms (table-driven) ----------
|
||||
|
||||
private static int oIdx(Orientation o) {
|
||||
return switch (o) {
|
||||
case DEFAULT -> 0;
|
||||
case _90 -> 1;
|
||||
case _180 -> 2;
|
||||
case _270 -> 3;
|
||||
};
|
||||
}
|
||||
|
||||
// rotate orientation clockwise / counterclockwise
|
||||
// index order: DEFAULT, _90, _180, _270
|
||||
private static final Orientation[] ORIENTATIONS = {
|
||||
Orientation.DEFAULT, Orientation._90, Orientation._180, Orientation._270
|
||||
};
|
||||
|
||||
private static Orientation rotateOrientation(Orientation o, int direction) {
|
||||
int i = oIdx(o);
|
||||
int ni = (direction == 1) ? (i + 1) : (i + 3); // -1 == +3 mod 4
|
||||
return ORIENTATIONS[ni & 3];
|
||||
}
|
||||
|
||||
// mirror tables: new orientation index after mirror
|
||||
private static final int[] ORIENT_MIRROR_H = { 2, 1, 0, 3 }; // DEFAULT<->_180, _90 stays, _270 stays
|
||||
private static final int[] ORIENT_MIRROR_V = { 0, 3, 2, 1 }; // _90<->_270, DEFAULT stays, _180 stays
|
||||
|
||||
private static Orientation mirrorOrientationHorizontal(Orientation o) {
|
||||
return ORIENTATIONS[ORIENT_MIRROR_H[oIdx(o)]];
|
||||
}
|
||||
|
||||
private static Orientation mirrorOrientationVertical(Orientation o) {
|
||||
return ORIENTATIONS[ORIENT_MIRROR_V[oIdx(o)]];
|
||||
}
|
||||
|
||||
// ---------- corner mapping (table-driven, matches BoxTrimmer.draw()) ----------
|
||||
|
||||
/**
|
||||
* CORNER_BY_FLAGS[orientationIndex][hvIndex] -> Corner
|
||||
* hvIndex encodes (h,v) as:
|
||||
* bit0 = h (0/1)
|
||||
* bit1 = v (0/1)
|
||||
* so:
|
||||
* 0: h=0,v=0
|
||||
* 1: h=1,v=0
|
||||
* 2: h=0,v=1
|
||||
* 3: h=1,v=1
|
||||
*
|
||||
* This matches draw() logic:
|
||||
* - DEFAULT, _90: normal (h: left->right, v: bottom->top)
|
||||
* - _180: horizontal meaning inverted
|
||||
* - _270: vertical meaning inverted
|
||||
*/
|
||||
private static final Corner[][] CORNER_BY_FLAGS = {
|
||||
// DEFAULT
|
||||
{ Corner.BL, Corner.BR, Corner.TL, Corner.TR },
|
||||
// _90
|
||||
{ Corner.BL, Corner.BR, Corner.TL, Corner.TR },
|
||||
// _180
|
||||
{ Corner.BR, Corner.BL, Corner.TR, Corner.TL },
|
||||
// _270
|
||||
{ Corner.TL, Corner.TR, Corner.BL, Corner.BR }
|
||||
};
|
||||
|
||||
private static final boolean[][][] FLAGS_BY_CORNER = buildFlagsByCorner();
|
||||
|
||||
private static boolean[][][] buildFlagsByCorner() {
|
||||
boolean[][][] out = new boolean[4][4][2]; // [ori][corner][h/v]
|
||||
for (int oi = 0; oi < 4; oi++) {
|
||||
for (int hv = 0; hv < 4; hv++) {
|
||||
Corner c = CORNER_BY_FLAGS[oi][hv];
|
||||
boolean h = (hv & 1) != 0;
|
||||
boolean v = (hv & 2) != 0;
|
||||
out[oi][c.ordinal()][0] = h;
|
||||
out[oi][c.ordinal()][1] = v;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Corner flagsToCorner(Orientation o, boolean h, boolean v) {
|
||||
int oi = oIdx(o);
|
||||
int hv = (h ? 1 : 0) | (v ? 2 : 0);
|
||||
return CORNER_BY_FLAGS[oi][hv];
|
||||
}
|
||||
|
||||
private static boolean[] cornerToFlags(Orientation o, Corner c) {
|
||||
int oi = oIdx(o);
|
||||
boolean h = FLAGS_BY_CORNER[oi][c.ordinal()][0];
|
||||
boolean v = FLAGS_BY_CORNER[oi][c.ordinal()][1];
|
||||
return new boolean[] { h, v };
|
||||
}
|
||||
|
||||
// ---------- corner transforms (table-driven) ----------
|
||||
|
||||
// ordinal order: TL=0, TR=1, BL=2, BR=3
|
||||
private static final Corner[] CORNER_ROT_CW = {
|
||||
Corner.TR, // TL -> TR
|
||||
Corner.BR, // TR -> BR
|
||||
Corner.TL, // BL -> TL
|
||||
Corner.BL // BR -> BL
|
||||
};
|
||||
|
||||
private static final Corner[] CORNER_ROT_CCW = {
|
||||
Corner.BL, // TL -> BL
|
||||
Corner.TL, // TR -> TL
|
||||
Corner.BR, // BL -> BR
|
||||
Corner.TR // BR -> TR
|
||||
};
|
||||
|
||||
private static final Corner[] CORNER_MIRROR_H = {
|
||||
Corner.TR, // TL <-> TR
|
||||
Corner.TL,
|
||||
Corner.BR, // BL <-> BR
|
||||
Corner.BL
|
||||
};
|
||||
|
||||
private static final Corner[] CORNER_MIRROR_V = {
|
||||
Corner.BL, // TL <-> BL
|
||||
Corner.BR, // TR <-> BR
|
||||
Corner.TL,
|
||||
Corner.TR
|
||||
};
|
||||
|
||||
private static Corner rotateCornerCW(Corner c) {
|
||||
return CORNER_ROT_CW[c.ordinal()];
|
||||
}
|
||||
|
||||
private static Corner rotateCornerCCW(Corner c) {
|
||||
return CORNER_ROT_CCW[c.ordinal()];
|
||||
}
|
||||
|
||||
private static Corner mirrorCornerHorizontal(Corner c) {
|
||||
return CORNER_MIRROR_H[c.ordinal()];
|
||||
}
|
||||
|
||||
private static Corner mirrorCornerVertical(Corner c) {
|
||||
return CORNER_MIRROR_V[c.ordinal()];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user