I apologize in advance. I am not good at explaining complex topics...
First, if you are not well-versed in Shape Meters, click
here to view the documentation for them.
Second, several values I am going to be talking about are usually provided through a LUA script, called SunCalc. However, for debugging purposes, I have deleted all references to this script and converted those values into manually editable variables:
#moonViewAngle#,
#moonDialAngle, and
#moonPhase#.
The Moon Icon
Before I can get to the actual issue, I need to explain how the moon icon itself works.
Normally, there are three shapes involved:
Border,
Background, and
Limb, which combine to make the icon:
2018-12-31 13_55_44-D__Settings_Caleb_Rainmeter_Skins_ModernGadgets_Settings_UnitTests_WeatherShapes.png
The border and background are simple circles, but the limb is a custom Path shape. Here is the standalone code for the moon icon, without all of the changes necessary to implement it into AstroDial:
astrodial-standalone-moon.gif
ShapeMoon.ini:
Code: Select all
[Rainmeter]
MiddleMouseUpAction=[!Refresh]
AccurateText=1
[Variables]
; Moon phase value: 0.0 (new) -> 0.5 (full) -> 1.0 (new)
moonPhase=0.0
; Angle that the illuminated limb of the moon will be displayed at (starting at north, going clockwise)
moonViewAngle=-131
; Set to 1 to use moonViewAngle, 0 to display horizontally
showMoonAngle=0
; Internal moon variables
moonRadius=60
moonBorderThickness=4
moonInnerRadius=(#moonRadius# - #moonBorderThickness#)
; Colors
colorBg=15,15,15
colorMoon=200,200,200
colorMoonBg=30,30,30
colorMoonBorder=70,70,70
; Converts the moon phase value into a radius value usable by the arc shape
[MeasureMoonShapePhaseCalc]
Measure=Calc
Formula=clamp(abs((#moonInnerRadius# * 4 * ((#moonPhase# > 0.5) ? (0.5 - (#moonPhase# - 0.5)) : #moonPhase#)) - #moonInnerRadius#),0,#moonInnerRadius#)
DynamicVariables=1
[MeterMoonShape]
Meter=Shape
Shape=Ellipse #moonRadius#,#moonRadius#,#moonRadius#,#moonRadius# | StrokeWidth 0 | Fill Color #colorMoonBorder#
Shape2=Ellipse #moonRadius#,#moonRadius#,#moonInnerRadius#,#moonInnerRadius# | StrokeWidth 0 | Fill Color #colorMoonBg#
Shape3=Path MoonArc | StrokeWidth 0 | Fill Color #colorMoon# | Rotate ((#showMoonAngle# = 1) ? (-#moonViewAngle#) : ((#moonPhase# > 0.5) ? -90 : 90)),#moonInnerRadius#,#moonInnerRadius# | Offset #moonBorderThickness#,#moonBorderThickness# | StrokeLineJoin Round
MoonArc=0,#moonInnerRadius# | ArcTo (#moonInnerRadius# * 2),#moonInnerRadius#,#moonInnerRadius#,[MeasureMoonShapePhaseCalc:],0,(((#moonPhase# > 0.25) && (#moonPhase# < 0.75)) ? 1 : 0) | ArcTo 0,#moonInnerRadius#,#moonInnerRadius#,#moonInnerRadius#,0,1
Padding=10,10,10,10
SolidColor=#colorBg#
DynamicVariables=1
LeftMouseUpAction=[!CommandMeasure MeasureMoonShapeActionTimer "Execute 1"]
; Temporary measure for debug purposes - animates between new moon and full moon, then back
[MeasureMoonShapeActionTimer]
Measure=Plugin
Plugin=ActionTimer
ActionList1=Repeat Increase, 16, 100 | Wait 1000 | Repeat Increase, 16, 100 | Reset
Increase=[!SetVariable moonPhase "(#moonPhase# + 0.005)"][!UpdateMeasure MeasureMoonShapePhaseCalc][!UpdateMeter MeterMoonShape][!UpdateMeasure MeasureMoonShapeActionTimer][!Redraw]
Reset=[!SetVariable moonPhase 0][!UpdateMeasure MeasureMoonShapePhaseCalc][!UpdateMeter MeterMoonShape][!UpdateMeasure MeasureMoonShapeActionTimer][!Redraw]
DynamicVariables=1
To create the limb, I use a Path to create a closed shape. The first segment is an arc shape that spans the width of the moon in the center (where the terminator line is in the example GIF above). This arc gets its Y radius from an equation that translates the SunCalc moon phase value (represented by the
#moonPhase# variable) into said radius. Here is the equation:
Code: Select all
; Converts the moon phase value into a radius value usable by the arc shape
[MeasureMoonShapePhaseCalc]
Measure=Calc
Formula=clamp(abs((#moonInnerRadius# * 4 * ((#moonPhase# > 0.5) ? (0.5 - (#moonPhase# - 0.5)) : #moonPhase#)) - #moonInnerRadius#),0,#moonInnerRadius#)
DynamicVariables=1
Group=MoonShape
For the second segment, I simply use another arc that goes back to the starting point (the left side of the moon) in an arc that perfectly matches up with the intersection between the background border and the background itself.
2018-12-31 14_26_23-D__Settings_Caleb_Rainmeter_Skins_ModernGadgets_Settings_UnitTests_WeatherShapes.png
Now that I have the shape of the moon's illuminated limb correct, I need to rotate it to the correct angle. There are two options for this: If the
#showMoonAngle# variable is set to 0, then the icon will default to displaying the moon from right to left, like so:
astrodial-standalone-showmoonangle-off.gif
It does this by checking the moon phase value. If it is between 0 and 0.5, that means that it is the first half of the moon cycle, so it rotates the limb 90 degrees clockwise. Once it passes full moon (
#moonPhase# > 0.5), it rotates the limb to point the other way (from north, 90 degrees counterclockwise).
If
#showMoonAngle# is set to 1, then the moon will rotate itself based on the
#moonViewAngle# variable. This is another substitute SunCalc value that, when used with the actual script, defines the angle of the moon's illuminated limb from north, going counterclockwise, as it appears in real life. In other words, the limb will face the same direction as the actual moon is facing.
For example, if the moon's angle is 131 degrees, it will look like this:
astrodial-standalone-showmoonangle-on.gif
Alright, so now you know how the moon icon itself works. Let's move on to implementing it into AstroDial.
Implementing the Moon Icon into AstroDial
A (rather annoying) limitation of shape meters is that you can only perform one transformation of each type on a shape (see the
Transformation Modifiers section of the Shape documentation). Therefore, since I already rotated the moon limb to get the correct angle, I cannot then go rotate it around the dial.
One way to circumvent this is to use the
Combine shape. This shape will literally "combine" two or more shapes into a new shape. When the combining happens, all attribute modifiers are taken from the parent shape. Therefore, I cannot simply combine the border, background, and limb shapes into one, because then it looks like this:
astrodial-standalone-moon-combined-badly.png
Because of this, in order to use combine to be able to rotate the limb twice, I need to create another dummy background, and use the
Intersect combine type to make it work:
astrodial-standalone-moon-combined-correctly.png
Since I didn't combine the border and background shapes, I will still need to rotate them separately from the limb shape. However, this is pretty easy, and works perfectly 100% of the time.
For all three shapes, I define the center of rotation for the dial, represented by the green dot:
astrodial-rotation-reference.png
To define the center of rotation, Rainmeter will start at the top-left of the shape's bounding box, and offset it by a certain number of pixels that you define. In this case, I offset it by the radius of the moon icon (
#dialObjectRadius#), then add the dial radius to that. Then I go down by
#dialObjectRadius#, so that it is located at the exact bottom-center of the dial arc.
astrodial-center of rotation reference-no limb rotation.png
Now that we have a center of rotation, all we need to do is tell it how many degrees to rotate. Do that, and you end up with this:
astrodial-rotation reference-130 degrees.png
Oh no! As you can see, when we rotate it around the dial, it adds that rotation to the limb as well. We can counter this simply by adding
#moonDialAngle to
#moonViewAngle#:
astrodial-center of rotation reference-limb rotation-broken.png
...And now we're to the original issue that I created this thread for.
Correcting for Bounding Box Movement
After much thought, and going back through and explaining to myself exactly how everything worked, I realized the problem:
astrodial-center of rotation reference-limb rotation.png
The issue lies with the limb rotation itself. As you can see in the above picture, when the limb is rotated, it changes the starting position of the limb shape's bounding box. This causes the defined center of dial rotation to be offset as well, which in turn, causes the dial rotation for the limb to become skewed. Thus, you end up with this (click to animate):
atrodial PROBLEMS.gif
So, how to fix it? I did come up with a partial solution. I realized that the
sine of
#moonViewAngle# would describe exactly how far away from the center of the icon that the top of the bounding box is, proportional to the full distance of
#moonInnerRadius# (
#dialObjectRadius# - #moonBorderThickness#). Same for
cosine in the X direction:
astrodial-moonlimbbboffset-sinecosineexplain.png
Using this, I was able to counteract the offset and return the center of dial rotation back to where it is supposed to be:
astrodial-centerofrotationreference-limbrotation-withoffset.png
All appeared to be well in the world. I did some tests and it appeared to be fixed:
astrodial less-than-half fixed.gif
...That is, until I tried it with a moon phase that was greater than first quarter:
astrodial more-than-half broken.gif
The Current Problem
FINALLY, AT LAST, comes the problem that I haven't yet figured out how to solve. For phases greater than a quarter, taking the sine and cosine of
#moonViewAngle# no longer accurately represents where the bounding box is:
astrodial-moonlimbbboffset-offsetnotright.png
The problem here is that the sine and cosine calculations depend on a straight line when referencing the angle (you can see this line by setting the phase to 0.25, or half moon). However, once you go greater than a half moon, this line is enveloped by the limb and can no longer be used as a reference point for the trigonometry. Thus, when it tries to calculate the values based on this "line", it doesn't work because the bounding box is no longer defined by that line.
This is where I am completely stuck. I hope the explanation made sense, and gives you a starting point to be able to help solve this problem. Thanks for reading my long-winded explanation, and thanks for having interest in helping me solve this.
AstroDial.ini:
Code: Select all
[Rainmeter]
MiddleMouseUpAction=[!Refresh]
AccurateText=1
[Variables]
; =========================
; DEBUG TESTING VARIABLES
; Sun / Moon Angles on the Dial (degrees)
sunDialAngle=-1
moonDialAngle=0
; Angle that the illuminated limb of the moon will face (degrees, starts North, counterclockwise)
rawMVA=220
; If set to 1, the moon will orient based on the value above. If set to 0, waxing will display on the right, and waning will display on the left.
showMoonAngle=1
; Moon phase value: 0.0 (new) -> 0.5 (full) -> 1.0 (new)
moonPhase=0.37
; =========================
; Colors
colorBg=15,15,15
colorDialArc=150,150,150
colorMoon=200,200,200
colorMoonBg=30,30,30
colorMoonBorder=70,70,70
colorSun=250,222,110
; Dial settings
dialRadius=250
dialStartX=3
dialArcThickness=6
dialObjectRadius=23
; Moon settings
moonBorderThickness=3
moonInnerRadius=(#dialObjectRadius# - #moonBorderThickness#)
moonArcStartX=(#dialStartX# - #dialObjectRadius#)
moonArcStartY=(#dialRadius# - #moonBorderThickness#)
moonViewAngle=((#rawMVA# + #moonDialAngle#) % 360)
; ==================================================
; MEASURES
; ==================================================
[MeasureSunDialVisibility]
Measure=Calc
Formula=#sunDialAngle#
IfCondition=(MeasureSunDialVisibility > 180) || (MeasureSunDialVisibility < 0)
IfTrueAction=[!SetVariable sunDialVisibility 0][!UpdateMeter MeterAstroDial][!Redraw]
IfFalseAction=[!SetVariable sunDialVisibility 255][!UpdateMeter MeterAstroDial][!Redraw]
DynamicVariables=1
[MeasureMoonDialVisibility]
Measure=Calc
Formula=#moonDialAngle#
IfCondition=(MeasureMoonDialVisibility > 180) || (MeasureMoonDialVisibility < 0)
IfTrueAction=[!SetVariable moonDialVisibility 0][!UpdateMeter MeterAstroDial][!Redraw]
IfFalseAction=[!SetVariable moonDialVisibility 255][!UpdateMeter MeterAstroDial][!Redraw]
DynamicVariables=1
; Converts the moon phase value into a radius value usable by the arc shape
[MeasureMoonShapePhaseCalc]
Measure=Calc
Formula=clamp(abs((#moonInnerRadius# * 4 * ((#moonPhase# > 0.5) ? (0.5 - (#moonPhase# - 0.5)) : #moonPhase#)) - #moonInnerRadius#),0,#moonInnerRadius#)
DynamicVariables=1
Group=MoonShape
; Adjusts the center of arc rotation for the moon limb shape in the X direction
[MeasureMoonLimbArcRotationXOffsetCalc]
Measure=Calc
DynamicVariables=1
Formula=(((#moonViewAngle# > 180) && (#moonViewAngle# < 360)) ? (#moonInnerRadius# * abs(cos(rad(#moonViewAngle#)))) : #moonInnerRadius#)
Group=MoonShape
; Adjusts the center of arc rotation for the moon limb shape in the Y direction
[MeasureMoonLimbArcRotationYOffsetCalc]
Measure=Calc
DynamicVariables=1
Formula=(((#moonViewAngle# <= 90) || (#moonViewAngle# >= 270)) ? #moonInnerRadius# : (#moonInnerRadius# * abs(sin(rad(#moonViewAngle#)))))
Group=MoonShape
; TEMPORARY DEBUG - rotates the moon's illuminated limb 360 degrees
[MeasureActionTimer]
Measure=Plugin
Plugin=ActionTimer
ActionList1=Repeat Increase, 16, 180
ActionList2=ResetMove | Repeat Move, 16, 180
ActionList3=ResetMove | Repeat Both, 16, 180
ActionList4=Repeat Increase, 16, 5
Increase=[!SetVariable rawMVA "(#rawMVA# + 2)"][!SetVariable moonViewAngle "((#rawMVA# + #moonDialAngle#) % 360)"][!UpdateMeasureGroup MoonShape][!UpdateMeter MeterAstroDial][!UpdateMeasure MeasureActionTimer][!Redraw]
Move=[!SetVariable moonDialAngle "(#moonDialAngle# + 1)"][!SetVariable moonViewAngle "((#rawMVA# + #moonDialAngle#) % 360)"][!UpdateMeasureGroup MoonShape][!UpdateMeter MeterAstroDial][!UpdateMeasure MeasureActionTimer][!Redraw]
Both=[!SetVariable rawMVA "(#rawMVA# + 2)"][!SetVariable moonDialAngle "(#moonDialAngle# + 1)"][!SetVariable moonViewAngle "((#rawMVA# + #moonDialAngle#) % 360)"][!UpdateMeasureGroup MoonShape][!UpdateMeter MeterAstroDial][!UpdateMeasure MeasureActionTimer][!Redraw]
ResetMove=[!SetVariable moonDialAngle 0][!UpdateMeasureGroup MoonShape][!UpdateMeter MeterAstroDial][!UpdateMeasure MeasureActionTimer][!Redraw]
DynamicVariables=1
; ==================================================
; METERS
; ==================================================
[MeterAstroDial]
Meter=Shape
; Arc
Shape=Arc #dialStartX#,#dialRadius#,(#dialRadius# * 2 + #dialStartX#),#dialRadius#,#dialRadius#,#dialRadius# | StrokeWidth #dialArcThickness# | Stroke Color #colorDialArc# | StrokeStartCap Round | StrokeEndCap Round
; Sun
Shape2=Ellipse #dialStartX#,#dialRadius#,#dialObjectRadius#,#dialObjectRadius# | StrokeWidth 0 | Fill Color #colorSun#,#sunDialVisibility# | Rotate #sunDialAngle#,(#dialRadius# + #dialObjectRadius#),(#dialObjectRadius#)
; Moon
Shape3=Ellipse #dialStartX#,#dialRadius#,#dialObjectRadius#,#dialObjectRadius# | StrokeWidth 0 | Fill Color #colorMoonBorder#,#moonDialVisibility# | Rotate #moonDialAngle#,(#dialRadius# + #dialObjectRadius#),(#dialObjectRadius#)
Shape4=Ellipse #dialStartX#,#dialRadius#,#moonInnerRadius#,#moonInnerRadius# | StrokeWidth 0 | Fill Color #colorMoonBg#,#moonDialVisibility# | Rotate #moonDialAngle#,(#dialRadius# + #moonInnerRadius#),(#moonInnerRadius#)
Shape5=Ellipse #dialStartX#,#dialRadius#,#moonInnerRadius#,#moonInnerRadius# | StrokeWidth 0 | Fill Color #colorMoon#,#moonDialVisibility#
Shape6=Path MoonArc | StrokeWidth 0 | Fill Color #colorMoon# | Rotate (((#showMoonAngle# = 1) ? (-#moonViewAngle#) : ((#moonPhase# > 0.5) ? -90 : 90))),#moonInnerRadius#,#moonInnerRadius# | Offset #moonBorderThickness#,#moonBorderThickness# | StrokeLineJoin Round
MoonArc=#moonArcStartX#,#moonArcStartY# | ArcTo (#moonArcStartX# + (#moonInnerRadius# * 2)),#moonArcStartY#,#moonInnerRadius#,[MeasureMoonShapePhaseCalc:],0,(((#moonPhase# > 0.25) && (#moonPhase# < 0.75)) ? 1 : 0) | ArcTo #moonArcStartX#,#moonArcStartY#,#moonInnerRadius#,#moonInnerRadius#,0,1
Shape7=Combine Shape5 | Intersect Shape6 | Rotate #moonDialAngle#,(#dialRadius# + [MeasureMoonLimbArcRotationXOffsetCalc:]),([MeasureMoonLimbArcRotationYOffsetCalc:])
Shape8=Ellipse #dialRadius#,#dialRadius#,2 | StrokeWidth 0 | Fill Color 0,150,0
Padding=30,30,30,30
SolidColor=#colorBg#
DynamicVariables=1
[MeterDebugLimbRotation]
Meter=String
FontFace=Arial
FontSize=12
FontColor=230,230,230
Padding=5,5,5,5
SolidColor=#colorBg#
Y=R
Text=Debug Limb Rotation
LeftMouseUpAction=[!CommandMeasure MeasureActionTimer "Execute 1"]
Antialias=1
[MeterDebugDialRotation]
Meter=String
FontFace=Arial
FontSize=12
FontColor=230,230,230
Padding=5,5,5,5
SolidColor=#colorBg#
Y=R
Text=Debug Dial Rotation
LeftMouseUpAction=[!CommandMeasure MeasureActionTimer "Execute 2"]
Antialias=1
[MeterDebugBoth]
Meter=String
FontFace=Arial
FontSize=12
FontColor=230,230,230
Padding=5,5,5,5
SolidColor=#colorBg#
Y=R
Text=Debug Both Rotations
LeftMouseUpAction=[!CommandMeasure MeasureActionTimer "Execute 3"]
Antialias=1
[MeterDebugStepLimb]
Meter=String
FontFace=Arial
FontSize=12
FontColor=230,230,230
Padding=5,5,5,5
SolidColor=#colorBg#
Y=R
Text=Debug Step Limb Rotation
LeftMouseUpAction=[!CommandMeasure MeasureActionTimer "Execute 4"]
Antialias=1
You do not have the required permissions to view the files attached to this post.