POV-Ray : Newsgroups : povray.animations : Dominoes : Re: Dominoes Server Time
6 May 2026 09:50:22 EDT (-0400)
  Re: Dominoes  
From: Tor Olav Kristensen
Date: 5 May 2026 18:00:00
Message: <web.69fa66a1cea3e23ebe9d2d3f89db30a9@news.povray.org>
"koppi" <jak### [at] gmailcom> wrote:
> "Leroy" <whe### [at] gmailcom> wrote:
> >  I was wondering how you place the dominoes. I use a spline for placement. I
> > have my dominoes placed 2 pov units apart. That because I don't use true physics
> > to move the dominoes.
>
> Archimedean spiral calculation is used to create the control points [1]:
>
>   [... line 47]:
>   local angle = (i - 1) * 0.09
>   local radius = 5 + angle * 0.95
>   cp = {
>     x = math.cos(angle) * radius,
>     y = y_pos,
>     z = math.sin(angle) * radius,
>   }
>   [...]
>
> The control points are then fed to the Catmull-Rom spline function [2] with the
> tension parameter set to 0.5.
>
> >  Noticed that your dominoes have different spacing, they got tighter toward the
> > center.
>
> I would like to have equal spacing between the dominoes, but I do not know how
> to calculate that, do you have an idea?
> ...

Below is an example in POV-Ray SDL that places dominoes at a fixed
distance from each other along an Archimedean spiral (without using
splines).

A Newton-Raphson solver macro is used to find the placement points
analytically for high precision.

The dominoes are also oriented by a Frenet-Serret frame so that they
always face the same side forward along the spiral.

--
Tor Olav
http://subcube.com
https://github.com/t-o-k


// ===== 1 ======= 2 ======= 3 ======= 4 ======= 5 ======= 6 ======= 7

#version 3.7;

global_settings {
    assumed_gamma 1
    ambient_light color rgb 0.2*<1, 1, 1>
}

#include "colors.inc"

#declare tau = 2*pi;

// ===== 1 ======= 2 ======= 3 ======= 4 ======= 5 ======= 6 ======= 7

// Newton-Raphson macro to find the root of a function.
// Used in this example to find the angle (Theta) that
// corresponds to a given arc length.

#macro NewtonRaphson(Fn, DerFn, EstimatedRoot, Tolerance, MaxIter)

    #local Nil = 1e-12;
    #local EstRoot = EstimatedRoot;
    #if (Tolerance <= 0)
        #local Tolerance = 1e-6;
    #end // if
    #local I = 0;
    #local Continue = true;
    #while (Continue)
        #local DerVal = DerFn(EstRoot);
        #local UseableTangent = (Nil < abs(DerVal));
        #if (UseableTangent)
            #local CloserRoot = EstRoot - Fn(EstRoot)/DerVal;
            #local Delta = abs(CloserRoot - EstRoot);
            #local EstRoot = CloserRoot;
            #local I = I + 1;
            #local Continue = ((Tolerance < Delta) & (I < MaxIter));
        #else
            #local Continue = false;
        #end // if
    #end // while

    EstRoot

#end // macro NewtonRaphson

// ===== 1 ======= 2 ======= 3 ======= 4 ======= 5 ======= 6 ======= 7

// Archimedes spiral (radius = g * th)

// _g: Growth rate (how fast the spiral spreads outward).
//     The radius increases by _g*tau mm per full revolution.
// _th: Rotation angle in radians.

// Position functions (calculates x and z from polar coordinates)
// <x, 0, z> are the cartesian coordinates for a point on the
// spiral at angle _th.
#declare FnX = function(_g, _th) { _g*_th*cos(_th) };
#declare FnZ = function(_g, _th) { _g*_th*sin(_th) };

// First derivatives of the functions (velocity),
// used to determine the tangent vector/direction of the curve.
#declare DerFnX = function(_g, _th) { _g*(cos(_th) - _th*sin(_th)) };
#declare DerFnZ = function(_g, _th) { _g*(sin(_th) + _th*cos(_th)) };


/*
Arc length function: Calculates the distance traveled along the spiral.
_g: Growth rate (how fast the spiral spreads outward).
_th: Final angle in radians (the angle the spiral has passed through).
*/

/*
#declare ArcLengthFn =
    function(_g, _th) {
        _g/2*(
            _th*sqrt(1 + pow(_th, 2)) +
            ln(_th + sqrt(1 + pow(_th, 2)))
        )
    }
;
#declare Asinh(_th) =
    function(_th) {
        ln(_th + sqrt(1 + pow(_th, 2)))
    }
;
*/

// Calculates the total distance traveled along the spiral curve,
// from the center to point the final angle.
#declare ArcLengthFn =
    function(_g, _th) {
        _g/2*(_th*sqrt(1 + pow(_th, 2)) + asinh(_th))
    }
;

// The first derivative of the arc length function:
#declare DerArcLengthFn =
    function(_g, _th) {
        _g*sqrt(1 + pow(_th, 2))
    }
;

// ===== 1 ======= 2 ======= 3 ======= 4 ======= 5 ======= 6 ======= 7

// Domino piece geometry (L x W x T: 48mm x 24mm x 10mm)
// Oriented so it stands on the xz plane, growing in y.
#declare DominoPiece =
    union {
        // Centered at origin
        box {
            -<12, 24, 5>, +<12, 24, 5>
            pigment { color White }
        }
        // Direction markers for debugging the matrix orientation:
        cylinder {
            +12*x, +16*x, 2  // Local +x (Normal)
            pigment { color Red }
        }
        cylinder {
            +24*y, +28*y, 2  // Local +y (Binormal)
            pigment { color Green }
        }
        cylinder {
            +5*z,  +9*z, 2  // Local +z (Tangent)
            pigment { color Blue }
        }
        // Move bottom of piece to y = 0
        translate +24*y
    }


#declare NoOfPoints = 300;  // Also the number of dominoes.

#declare GrowthRate = 60/tau;  // mm/radian
// The radial distance between centerlines of successive
// windings (the pitch) will be 60mm.

// Distance between centers of dominoes along the spiral path.
#declare StepLength = 25;  // mm


// Calculate angles (Thetas) for each domino using numerical inversion.

#debug "\n"
#declare Thetas = array[NoOfPoints];
#declare Thetas[0] = 0;
#for (I, 1, NoOfPoints - 1)
    #declare TargetLength = I*StepLength;
    #declare Thetas[I] =
        NewtonRaphson(
            function(_th) {
                ArcLengthFn(GrowthRate, _th) - TargetLength
            },
            function(_th) {
                DerArcLengthFn(GrowthRate, _th)
            },
            Thetas[I-1],  // Use previous angle as start estimate
            1e-6,
            10
        )
    ;
    #declare AchievedLength = ArcLengthFn(GrowthRate, Thetas[I]);
    #debug str(AchievedLength - TargetLength, 0, 14)
    #debug "\n"
#end // for
#debug "\n"


// Define specific instances of the functions
// for the chosen growth rate

#declare X_Fn = function(_th) { FnX(GrowthRate, _th) };
#declare Z_Fn = function(_th) { FnZ(GrowthRate, _th) };

#declare DerX_Fn = function(_th) { DerFnX(GrowthRate, _th) };
#declare DerZ_Fn = function(_th) { DerFnZ(GrowthRate, _th) };

/*
pP: Position on spiral
vT: Tangent vector (Forward direction)
vB: Binormal vector (Up direction)
vN: Normal vector (Side direction, perpendicular to curve)
*/

// Render the dominoes
union {
    #declare vB = vnormalize(<0, 1, 0>);  // = <0, 1, 0>
    // Start at 1 to skip the domino piece in the center
    #for (I, 1, NoOfPoints - 1)
        #declare Th = Thetas[I];
        #declare pP = <X_Fn(Th), 0, Z_Fn(Th)>;
        #declare vT = vnormalize(<DerX_Fn(Th), 0, DerZ_Fn(Th)>);
        #declare vN = vcross(vB, vT);
        object {
            DominoPiece
            matrix <
                vN.x, vN.y, vN.z, // Maps local x to Side
                vB.x, vB.y, vB.z, // Maps local y to Up
                vT.x, vT.y, vT.z, // Maps local z to Forward
                pP.x, pP.y, pP.z  // Translates to spiral position
            >
        }
    #end // for
}

// ===== 1 ======= 2 ======= 3 ======= 4 ======= 5 ======= 6 ======= 7

sky_sphere { pigment { color Gray } }

// A checkered floor plane to provide visual context and shadows
plane {
    y, 0  // The ground plane at y = 0
    pigment {
        checker
        color 0.05*White  // Dark squares
        color 0.60*White  // Light squares
        scale 60 // Each square matches the pitch of the spiral (60mm)
    }
}

light_source {
    100*<6, 14, 3>
    color White
}

#declare AspectRatio = image_width/image_height;

camera {
 orthographic
    location +1000*y
 right +900*AspectRatio*x
 up +900*z
    direction -y
}

/*
camera {
    location 120*<+1, +3, -5>
    look_at 100*< 0, -1,  0>
}
*/

// ===== 1 ======= 2 ======= 3 ======= 4 ======= 5 ======= 6 ======= 7


Post a reply to this message

Copyright 2003-2023 Persistence of Vision Raytracer Pty. Ltd.