diff --git a/CHANGELOG.md b/CHANGELOG.md index 7aa51547..3793b25c 100755 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,15 @@ # Change Log +## [1.7.1](https://github.com/nicklockwood/ShapeScript/releases/tag/1.7.1) (2023-10-23) + +- Fixed spotlight debug shape orientation +- Added `Geometry.withoutLights()` method +- Added `Geometry.withoutGroupTransform()` method +- Deprecated `Geometry.with(name:)` method +- Added `point.isCurved` and `polygon.center` member properties +- Added `object` type logging and made logging less error-prone +- Added documentation for lights limit introduced in 1.7.0 + ## [1.7.0](https://github.com/nicklockwood/ShapeScript/releases/tag/1.7.0) (2023-10-14) - Fixed ambiguity when calling functions with parenthesized arguments diff --git a/ShapeScript.podspec.json b/ShapeScript.podspec.json index 6a4075c3..c0b50776 100644 --- a/ShapeScript.podspec.json +++ b/ShapeScript.podspec.json @@ -1,6 +1,6 @@ { "name": "ShapeScript", - "version": "1.7.0", + "version": "1.7.1", "license": { "type": "MIT", "file": "LICENSE.md" @@ -10,7 +10,7 @@ "authors": "Nick Lockwood", "source": { "git": "https://github.com/nicklockwood/ShapeScript.git", - "tag": "1.7.0" + "tag": "1.7.1" }, "source_files": ["ShapeScript", "LRUCache/Sources", "SVGPath/Sources"], "requires_arc": true, diff --git a/ShapeScript.xcodeproj/project.pbxproj b/ShapeScript.xcodeproj/project.pbxproj index b58dc234..0469422c 100644 --- a/ShapeScript.xcodeproj/project.pbxproj +++ b/ShapeScript.xcodeproj/project.pbxproj @@ -1316,7 +1316,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.7.1; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer; PRODUCT_MODULE_NAME = Viewer; PRODUCT_NAME = "ShapeScript Viewer"; @@ -1344,7 +1344,7 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.7.1; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer; PRODUCT_MODULE_NAME = Viewer; PRODUCT_NAME = "ShapeScript Viewer"; @@ -1374,7 +1374,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.7.1; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer; PRODUCT_MODULE_NAME = Viewer; PRODUCT_NAME = ShapeScript; @@ -1406,7 +1406,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.7.1; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptViewer; PRODUCT_MODULE_NAME = Viewer; PRODUCT_NAME = ShapeScript; @@ -1570,7 +1570,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.7.1; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptLib; @@ -1607,7 +1607,7 @@ "@executable_path/../Frameworks", "@loader_path/Frameworks", ); - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.7.1; MODULE_VERIFIER_SUPPORTED_LANGUAGES = "objective-c objective-c++"; MODULE_VERIFIER_SUPPORTED_LANGUAGE_STANDARDS = "gnu11 gnu++14"; PRODUCT_BUNDLE_IDENTIFIER = com.charcoaldesign.ShapeScriptLib; diff --git a/ShapeScript/Interpreter.swift b/ShapeScript/Interpreter.swift index f0a8ed25..894042d6 100644 --- a/ShapeScript/Interpreter.swift +++ b/ShapeScript/Interpreter.swift @@ -11,7 +11,7 @@ import Foundation // MARK: Public interface -public let version = "1.7.0" +public let version = "1.7.1" public protocol EvaluationDelegate: AnyObject { func resolveURL(for path: String) -> URL diff --git a/Viewer/Mac/WhatsNew.rtf b/Viewer/Mac/WhatsNew.rtf index 34b90731..b6d3ce63 100644 --- a/Viewer/Mac/WhatsNew.rtf +++ b/Viewer/Mac/WhatsNew.rtf @@ -11,6 +11,31 @@ What's New in ShapeScript?\ \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\f1\b\fs28 \cf2 ShapeScript 1.7.1 \'97 2023-10-23\ +\ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\cf2 \kerning1\expnd0\expndtw0 \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Fixed spotlight debug shape orientation.\ +\ \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Added `Geometry.withoutLights()` method.\ +\ \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Added `Geometry.withoutGroupTransform()` method.\ +\ \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Deprecated `Geometry.with(name:)` method.\ +\ \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Added `point.isCurved` and `polygon.center` member properties.\ +\ \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Added `object` type logging and made logging less error-prone.\ +\ \'95 +\f0\b0 \expnd0\expndtw0\kerning0 + Added documentation for lights limit introduced in 1.7.0.\ +\ \f1\b\fs28 \cf2 ShapeScript 1.7.0 \'97 2023-10-14\ \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 diff --git a/Viewer/iOS/WhatsNew.rtf b/Viewer/iOS/WhatsNew.rtf index 8c1bdb3a..b808bbec 100644 --- a/Viewer/iOS/WhatsNew.rtf +++ b/Viewer/iOS/WhatsNew.rtf @@ -5,6 +5,32 @@ \paperw11900\paperh16840\margl1440\margr1440\vieww24140\viewh18420\viewkind0 \deftab720 +\f0\b \cf2 ShapeScript 1.7.1 \'97 2023-10-23\ +\ +\pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 +\cf2 \kerning1\expnd0\expndtw0 +\f1\b0 \expnd0\expndtw0\kerning0 \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Fixed spotlight debug shape orientation.\ +\ \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Added `Geometry.withoutLights()` method.\ +\ \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Added `Geometry.withoutGroupTransform()` method.\ +\ \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Deprecated `Geometry.with(name:)` method.\ +\ \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Added `point.isCurved` and `polygon.center` member properties.\ +\ \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Added `object` type logging and made logging less error-prone.\ +\ \'95 +\f1\b0 \expnd0\expndtw0\kerning0 + Added documentation for lights limit introduced in 1.7.0.\ +\ \f0\b \cf2 ShapeScript 1.7.0 \'97 2023-10-14\ \ \pard\tx220\tx720\pardeftab720\li720\fi-720\partightenfactor0 diff --git a/docs/1.7.1/ios/blocks.md b/docs/1.7.1/ios/blocks.md new file mode 100644 index 00000000..f98315ee --- /dev/null +++ b/docs/1.7.1/ios/blocks.md @@ -0,0 +1,84 @@ +Blocks +--- + +A block is a nested list of instructions, contained inside `{ ... }` braces. Some commands, such as [builders](builders.md) or [CSG operations](csg.md), accept a block parameter instead of a simple value like a number or [vector](literals.md#vectors-and-tuples). + +Instructions inside a block are executed within the [scope](scope.md) of the command that invoked them. Typically that means that any transforms or material changes made inside the block will only apply to geometry created inside the same block. This also applies to any symbols that you define inside the block. + +You can define your own blocks using the `define` command. Here is a block that creates a five-pointed star: + +```swift +define star { + path { + for 1 to 5 { + point 0 -0.5 + rotate 1 / 5 + point 0 -1 + rotate 1 / 5 + } + point 0 -0.5 + } +} +``` + +You can call it by simply referencing its name, like this: + +```swift +star +``` + +![Star](../../images/star.png) + +**Note:** there is a subtle distinction between the code above and the code below: + +```swift +define star path { + for 1 to 5 { + point 0 -0.5 + rotate 1 / 5 + point 0 -1 + rotate 1 / 5 + } + point 0 -0.5 +} +``` + +In the original code, we defined a new block symbol that creates a star-shaped path. In the code above we've defined a symbol whose value is a star-shaped path. The former code is evaluated at the point when it is *called*, whereas the latter code is evaluated at the point when it is *defined*. + +The end-result is the same in this case, so it may seem like the distinction doesn't matter, but the advantage of the former approach is that we can add *options* to vary the behavior of the code when it is called. + +## Options + +To add an option to a block, you use the `option` command. This works in a similar way to the [define](symbols.md) command, but it allows the specified value to be overridden by the caller. + +The code below extends the `star` definition with options for the radius and number of points: + +```swift +define star { + option radius 1 + option points 5 + path { + for 1 to points { + point 0 -0.5 + rotate 1 / points + point 0 -radius + rotate 1 / points + } + point 0 -0.5 + } +} +``` + +Now we can use those options to create a star with 6 points if we choose: + +```swift +star { + points 6 + radius 2 +} +``` + +![Star](../../images/six-pointed-star.png) + +--- +[Index](index.md) | Next: [Scope](scope.md) diff --git a/docs/1.7.1/ios/bounds.md b/docs/1.7.1/ios/bounds.md new file mode 100644 index 00000000..cc5e3bbc --- /dev/null +++ b/docs/1.7.1/ios/bounds.md @@ -0,0 +1,115 @@ +Bounds +--- + +ShapeScript's [size](transforms.md#size) ands [scale](transforms.md#relative-transforms) commands let you control the relative size of a shape, but sometimes it's useful to know the exact dimensions. + +A cube of size 1 has an easily-predicted size of one world unit square, but what about a more complex shape, such as a 5-pointed star (see the [procedural paths](paths.md#procedural-paths) and [blocks](blocks.md) sections for details): + +```swift +define star path { + for 1 to 5 { + point 0 -0.5 + rotate 1 / 5 + point 0 -1 + rotate 1 / 5 + } + point 0 -0.5 +} + +// draw star +extrude { + color red + star +} + +// draw cube +cube { + color green 0.5 +} +``` + +![Star with unit cube](../../images/star-with-unit-cube.png) + +We can see that the star is larger than the unit cube, but other than trial-and-error or complex math, how can we get the exact size? This is where the `bounds` [member property](expressions.md#members) comes in. + +## Mesh Bounds + +Paths and meshes both expose a `bounds` property that represents a bounding box around the shape. From this you can get the exact size and position needed to place a box around the star: + +```swift +define star { + ... +} + +// define star shape +define shape extrude { + color red + star +} + +// draw star +shape + +// draw box around star +cube { + color green 0.5 + position shape.bounds.center + size shape.bounds.size +} +``` + +![Star with fitted cube](../../images/star-with-fitted-cube.png) + +## Path Bounds + +In the example above we computed the bounds of a solid `mesh` (an extruded star-shaped `path`) but you can also get the bounds of a `path` directly. The following code draws the star path inside its bounding rectangle: + +```swift +define star { + ... +} + +// draw star +star + +// draw rectangle around star +square { + position shape.bounds.center + size shape.bounds.size +} +``` + +![Star with fitted rectangle](../../images/star-with-fitted-rect.png) + +## Bounds Members + +The `bounds` member property has the following sub-properties that you can use: + +* `min` - The position of the corner of the box with the smallest X, Y and Z values relative to the origin. +* `max` - The position of the corner of the box with the largest X, Y and Z values relative to the origin. +* `center` - The position of the center of the box relative to the origin. +* `size` - The size (width, height and depth) of the box in world units. +* `width` - The width of the box along the X axis (equivalent to `size.width`) +* `height` - The height of the box along the Y axis (equivalent to `size.height`) +* `depth` - The depth of the box along the Z axis (equivalent to `size.depth`) + +So, for example, to get the height of a shape, you could use: + +```swift +print someShape.bounds.size.height +``` + +or just: + +```swift +print someShape.bounds.height +``` + +And to get the X coordinate of its rightmost edge you could use: + +```swift +print someShape.bounds.max.x +``` + +--- +[Index](index.md) | Next: [Meshes](meshes.md) diff --git a/docs/1.7.1/ios/builders.md b/docs/1.7.1/ios/builders.md new file mode 100644 index 00000000..ba0bafcb --- /dev/null +++ b/docs/1.7.1/ios/builders.md @@ -0,0 +1,277 @@ +Builders +--- + +We explored some basic 3D shape types in the [primitives](primitives.md) section, but the real power of ShapeScript comes from the ability to define custom shapes. + +In the [paths](paths.md) section we looked at how to define custom shapes using paths. ShapeScript has a variety of built-in commands for creating 3D shapes from paths, called *builders*: + +## Fill + +The most basic shape builder is the `fill` command, which creates a filled polygon from a path. Using the pentagon path we defined earlier, we can use `fill` to create a solid pentagon: + +``` +fill path { + for 0 to 5 { + point 0 1 + rotate 2 / 5 + } +} +``` + +![Filled Pentagon](../../images/filled-pentagon.png) + +Unlike a path, a filled shape can have a color and texture, but it has zero thickness. + +If a path contains multiple overlapping sub-paths, they will be filled using the [even-odd rule](https://en.wikipedia.org/wiki/Even–odd_rule). For example, the [overlapping circles](paths.md#nested-paths) path would be filled like this: + +```swift +fill path { + circle + translate 0.5 + scale 0.5 + circle +} +``` + +![Even-odd Fill](../../images/even-odd-fill.png) + +## Lathe + +The `lathe` command creates a 3D shape from a 2D path by revolving it around the Y axis. + +To use `lathe`, you must first define a suitable path. For example, the following code defines the *profile* (one half of the outline) of a chess piece: + +```swift +path { + curve 0 0.78 + curve -0.15 0.7 + curve -0.15 0.5 + point -0.07 0.45 + curve -0.12 0.2 + point -0.25 0.1 + point -0.25 0 + point 0 0 +} +``` + +![Pawn outline](../../images/pawn-profile.png) + +When passed to the `lathe` command, the path creates a solid 3D shape: + +```swift +lathe path { + curve 0 0.78 + curve -0.15 0.7 + curve -0.15 0.5 + point -0.07 0.45 + curve -0.12 0.2 + point -0.25 0.1 + point -0.25 0 + point 0 0 +} +``` + +![Pawn](../../images/pawn.png) + +As with other curved shapes such as the `sphere` and `cone`, the smoothness of the lathed surface can be controlled using the `detail` and `smoothing` commands. + +The path describing the profile of the lathed shape was in this case open-ended. Since the ends meet at the Y axis anyway, the resultant shape will still be closed. Open paths that do not touch the axis will produce a hollow shape with visible holes. + +Lathed paths must lie flat on the XY plane, and be positioned entirely to one side of the Y axis. Any points on the path with a non-zero Z coordinate will be flattened, and parts of the path that cross the Y axis will be clipped before lathing. + +## Extrude + +The `extrude` command extrudes a 2D path along the Z axis. The path therefore represents a cross-section of the final shape. The default extrusion distance is 1 unit, but it can be overridden using the `size` option or relative `scale` command (as described in [transforms](transforms.md)). + +In the following example, `extrude` is used to create a triangular prism: + +```swift +extrude { + size 1 1 2 // increase extrusion distance to 2 units + polygon { sides 3 } +} +``` + +![Prism](../../images/prism.png) + +You can also extrude a shape *along* another path using the `along` option. In the following example, a circle is extruded along a rounded rectangle: + +```swift +extrude { + circle + along roundrect { + size 5 + } +} +``` + +![Rounded rectangle with circle cross-section](../../images/roundrect-extrusion.png) + +In the following example, we extrude a square cross-section along a 3D path: + +```swift +define shape path { + orientation 0 0 -0.4 + curve 0 1 0.75 + curve -1 0 + curve 0 -1 0.25 + curve 1 0 + curve 1 1 + curve 0 1 0.75 +} + +extrude { + square { size 0.1 } + along shape +} +``` + +![Tangential extrusion](../../images/tangential-extrusion.png) + +Note how the cross-section tilts as it follows the path. This can be a nice effect, but you might prefer that the cross section remains perpendicular to the world axes. To control this, you can use the `axisAligned` property: + +```swift +extrude { + square { size 0.1 } + along shape + axisAligned true +} +``` + +![Axis-aligned extrusion](../../images/axis-aligned-extrusion.png) + +A value of `true` for `axisAligned` means the cross section will remain perpendicular to the path, which in some cases may cause the shape to look "pinched". + +A value of `false` means the cross-section is always aligned to the tangent of the path, which ensures consistent thickness at the cost of inconsistent orientation. + +If the `axisAligned` property is omitted, ShapeScript will try to choose an appropriate alignment for the path provided to `along`. + +To apply a twist along the length of an extruded shape, you can use the `twist` property: + +```swift +extrude { + square + twist 0.5 +} +``` + +![Twisted extrusion](../../images/twisted-extrusion.png) + +Twist uses the same [half-turn](transforms.md#orientation) units as the other rotation commands, so a twist of 0.5 equates to one quarter turn, or 90 degrees. + +Applying a twist will automatically increase the number of cross sections in proportion to the current [detail](options.md#detail) level to prevent distortion: + +![Twisted extrusion wireframe](../../images/twisted-extrusion-wireframe.png) + +You can combine `twist` with the `along` property to twist a curved path: + +```swift +extrude { + square { size 0.2 } + along circle + twist 2 +} +``` + +![Twisted circle](../../images/twisted-circle.png) + +**Note:** when extruding along a closed path, you should always use a multiple of `2` (i.e. a full rotation) for the `twist` value to avoid an ugly seam: + +![Twisted circle seam](../../images/twisted-circle-seam.png) + + +## Loft + +The `loft` command is similar to `extrude`, but instead of taking a single cross-section path and extruding it by a fixed distance, `loft` accepts multiple cross-sections and then joins them together to form a solid shape. + +For `loft` to work correctly, the specified paths must *not* all be located on the same plane, otherwise the resultant shape will be flat. You can either provide non-zero Z values for your path points, or use the `translate` command to space out your paths. + +For example, the following code produces a prism equivalent to the extrusion example above: + +```swift +loft { + // triangle 1 + polygon { sides 3 } + + translate 0 0 2 + + // triangle 2 + polygon { sides 3 } +} +``` + +You can also apply rotations and scaling between cross sections, to create bends or curves. For example, the following code creates a torus: + +```swift +define steps 32 +define radius 1 // radius of ring + +loft { + for 0 to steps { + circle { size 0.25 } + rotate 0 0 -1/steps + translate 0 0 (2 * pi * radius / steps) + rotate 0 0 -1/steps + } +} +``` + +![Torus](../../images/torus.png) + +However, the real power of the `loft` command comes from the fact that you can use *different* cross-sections, and the resultant surface will be interpolated between them. For example, here is a shape whose cross-section is square at one end and circular at the other: + +```swift +loft { + square + translate 0 0 1 + circle +} +``` + +![Loft shape](../../images/loftshape.png) + +## Hull + +The `hull` command works a bit like `loft`, in that it joins together multiple paths to form a solid surface. Here is the `loft` shape above, implemented using `hull`: + +```swift +hull { + square + translate 0 0 1 + circle +} +``` + +![Hull shape](../../images/loftshape.png) + +So if `hull` and `loft` are the same, why have a separate command? + +The difference is that rather than joining sections together in a tube, `hull` forms a tight *skin* around the outside of all its child shapes, known as a [convex hull](https://en.wikipedia.org/wiki/Convex_hull). To illustrate the difference, here is the `hull` of a star shape, vs a `loft` of the same shape: + +![Hull shape](../../images/hull-vs-loft.png) + +But the real power of the `hull` command is that it's not limited to operating on paths. Unlike the other builders, you can create a hull around meshes, paths, points, or any combination. For example, here is a hull formed from a cylinder and a point: + +```swift +hull { + cylinder + point 1 0 0 +} +``` + +![Hull shape](../../images/hull.png) + +This allows for a lot of interesting shapes that would be hard to create using the other commands. + +You might notice some odd stripes on the surface near the lip of the cylinder. As you may recall from the [detail](options.md#detail) section, curved surfaces in ShapeScript are actually formed from straight-edged polygons, with lighting used to make them appear smooth. + +When creating a hull from different shapes, the resultant [surface normals](https://en.wikipedia.org/wiki/Normal_(geometry)) aren't always what you'd expect, which can result in lighting glitches like this. In some cases you can use the [smoothing](options.md#smoothing) command to smooth out these discrepancies, like so: + +```swift +smoothing 0.25 +``` + +![Hull shape](../../images/smoothed-hull.png) + +--- +[Index](index.md) | Next: [Constructive Solid Geometry](csg.md) diff --git a/docs/1.7.1/ios/camera-control.md b/docs/1.7.1/ios/camera-control.md new file mode 100644 index 00000000..1854db83 --- /dev/null +++ b/docs/1.7.1/ios/camera-control.md @@ -0,0 +1,30 @@ +Camera Control +--- + +When you open a file in ShapeScript, it is rendered in 3D using a virtual camera, which can be controlled either programmatically or via touch gestures. + +## Camera Selection + +The camera defaults to "Front" view, which is positioned along the Z axis, looking forward towards the scene. The distance of the camera from the origin is set automatically based on volume occupied by the shapes in the scene. + +You can choose a different camera angle from the camera menu in the top right of the screen. [Custom cameras](cameras.md#custom-cameras) can be defined programatically. ShapeScript will remember the last selected camera for each shape file. + +To temporarily change the camera position, you can alter the view using touch gestures (see below). To reset the current camera back to its original position, select `Reset View`. + +## Touch Controls + +Motion | Action +:--------------------------- | :-------------------------- +Rotate scene | Swipe with one finger +Pan up/down/left/right | Swipe with two fingers +Zoom in and out | Pinch +Roll | Pinch, then rotate fingers + +## Copy Settings + +It can be difficult to visualize the effect that a given position or orientation will have when defining a [custom camera](cameras.md#custom-cameras). A good solution is to use touch gestures to position the camera, then select `Copy Settings` from the camera menu. + +This copies a snippet of ShapeScript code which you can then paste into your `.shape` file to create the custom camera. + +--- +[Index](index.md) | Next: [Primitives](primitives.md) diff --git a/docs/1.7.1/ios/cameras.md b/docs/1.7.1/ios/cameras.md new file mode 100644 index 00000000..ae8dda22 --- /dev/null +++ b/docs/1.7.1/ios/cameras.md @@ -0,0 +1,132 @@ +Cameras +--- + +ShapeScript provides a number of built-in cameras for viewing your scene from the front, back, side, etc. But you may wish to view the scene from a custom viewpoint (say, a 45-degree overhead angle). + +While it is possible to reposition the view using the [camera controls](camera-control.md), it's difficult to do so in a reproducible way, which is especially important if you wish to [export](export.md) an image of your scene. + +## Custom Cameras + +To solve this, in addition to the built-in cameras, ShapeScript also provides a syntax for defining your own cameras. To define a custom camera, use the `camera` command: + +```swift +camera +``` + +The location of the camera in the file is mostly unimportant - it will behave the same if it is placed before or after the visible geometry. Cameras added to your `.shape` file will appear in the camera menu below the other cameras. + +![Custom camera menu](../../images/custom-camera-menu-ios-1.6.4.png) + +You may add multiple cameras to a scene and they will be named "Custom", "Custom 2", "Custom 3", and so on. You can use the `name` command to assign a more meaningful name to a given camera, and this will be reflected in the menu: + +```swift +camera { + name "Hello" +} +``` + +You can configure the camera position and orientation programmatically using the properties described below, however the easiest way to define a custom camera is to position it using the camera controls and then [copy the configuration](camera-control.md#copy-settings) and paste it into your `.shape` file. + +## Position + +By default the camera will be positioned just in front of the scene, but you can adjust its position using the `position` property: + +```swift +camera { + position 3 3 3 +} +``` + +This code creates a camera at the coordinates `3,3,3`, which is diagonally up, right, and forwards from the center of the scene. Since we have not specified otherwise, the camera will point in the direction of the center of the scene, so you might see something like this: + +![Custom camera](../../images/custom-camera.png) + +## Orientation + +To change the orientation of the camera, and an `orientation` property: + +```swift +camera { + position 3 3 3 + orientation 0 -0.15 0.25 +} +``` + +This rotates the camera by 27 degrees to the left (`0.15 * 180`) and 45 degrees down (`0.25 * 180`), so that it is pointing roughly at the cone: + +![Custom camera orientation](../../images/camera-orientation.png) + +Cameras are also affected by [relative transforms](transforms.md#relative-transforms), so the following would be equivalent to the above: + +```swift +translate 3 3 3 +rotate 0 -0.15 0.25 +camera { + position 0 + orientation 0 +} +``` + +**Note:** the `position 0` and `orientation 0` here are required, because if these are not specified then the camera position and orientation will be set to their default values. + +## Field of View + +In addition to position and orientation, you can also adjust the *field of view* using the `fov` property: + +```swift +camera { + position 3 3 3 + orientation 0 -0.15 0.25 + fov 0.25 +} +``` + +If you imagine your view as a cone expanding out from the camera, the field of view is the angle between the top and bottom edges of the cone. Like all angles in ShapeScript, it's measured in multiples of 180 degrees, so `fov 0.25` sets the field of view to 45 degrees - slightly narrower than the default of 60 degrees. This has the effect of *zooming-in* the camera: + +![Custom field of view](../../images/custom-fov.png) + +You might think that the same effect could be achieved by simply moving the camera closer to the center of the scene, but in practice the result of this looks quite different due to the effect of perspective. To eliminate the effect of perspective, you can use an *orthographic* camera, which displays the scene without any perspective distortion. + +## Orthographic View + +To use an orthographic camera, you can either toggle the `Orthographic` menu (which affects all cameras), or you can set `fov` to zero or a negative value in your script, which will override the global setting. + +![Orthographic view](../../images/orthographic-camera.png) + +When using an orthographic projection, the distance of the camera from an object no longer affects its size on screen. In order to control the scale at which objects appear, you can use the `size` property of the camera: + +```swift +camera { + position 3 3 3 + orientation 0 -0.15 0.25 + size 5 +} +``` + +As with `position` and `orientation`, if the `size` property is omitted then a suitable orthographic scale will be determined automatically. + +## Background + +The [background color or texture](commands.md#background) for the scene can be overridden for each camera. This opens up some useful possibilities such as setting orthographic sketches as background for different cameras to use as reference images when modeling: + +```swift +camera { + name "Side View" + position 5 0 0 + background "side-view.jpg" +} +``` + +## Pixel Dimensions + +You can add an optional `width` and/or `height` to your camera block to control the pixel dimensions of the captured image. This has no effect on how the camera view is displayed in the app, but determines the size of [exported images](export.md#image-formats): + +```swift +camera { + width 1024 + height 768 +} +``` + +--- +[Index](index.md) | Next: [Comments](comments.md) diff --git a/docs/1.7.1/ios/commands.md b/docs/1.7.1/ios/commands.md new file mode 100644 index 00000000..80adec96 --- /dev/null +++ b/docs/1.7.1/ios/commands.md @@ -0,0 +1,122 @@ +Commands +--- + +Like [options](options.md), and [functions](functions.md), commands in ShapeScript are denoted by a keyword followed by zero or more values or expressions. Different commands accept different value types, but typically these will be a number, vector, text or [block](blocks.md). + +```swift +detail 5 // a numeric argument + +translate 1 0 -1 // a vector argument + +texture "world.png" // a text argument +``` + +The values passed to a command can be anything from simple literal values, to symbol references, to whole expressions. For example: + +```swift +rotate 1 + angle 0 0 +``` + +In the case above, the `roll` parameter of the `rotate` command has been given a value of `1 + angle`, and the `yaw` and `pitch` parameters are zero. Expressions can be a bit confusing to read inside a tuple of parameters due to the differing significance of spaces between values, so for clarity you may wish to add parentheses: + +```swift +rotate (1 + angle) 0 0 +``` + +Commands do not *necessarily* accept or return a value. The main distinction between commands and functions/constants is that commands have *side effects*. Typically they either alter the appearance of the shape, or alter the effects of subsequent commands. Here are some examples: + +ShapeScript has a number of built-in commands: + +## Background + +The `background` command can be used to set the background color of the rendered scene. For example: + +```swift +background 1 0 0 // set background color to red +``` + +Instead of a color, you can pass a file path to an image: + +```swift +background "filename.png" // set background image +``` + +This affects how the scene appears in the ShapeScript viewer, as well as the background of [exported images](export.md#image-formats). By default, the background is transparent. + +**Note:** the `background` command can only be used at the root level of a ShapeScript file, or inside a [camera](cameras.md) node: + +## Detail + +The `detail` command can be used anywhere in the ShapeScript file to override the local detail level used for approximating curved geometry. It is documented in the [options](options.md#detail) section. + +## Smoothing + +The `smoothing` command can be used anywhere in the ShapeScript file to override the local smoothing level used for calculating surface normals. It is documented in the [options](options.md#smoothing) section. + +## Materials + +The `color`, `texture` and `opacity` commands are used to specify the appearance of shapes when rendered. These commands are documented in the [materials](materials.md) section. + +## Font + +The `font`, command is used to specify the font used to render [text](text.md). + +## Transforms + +The `rotate`, `translate` and `scale` commands are useful for procedurally generating paths and complex shapes. These are documented in the [transforms](transforms.md#relative-transforms) section. + +## Primitives + +The `cube`, `sphere`, `cone` and `cylinder` commands are used to generate simple 3D shapes that can be composed into more complex forms. They are documented in the [primitives](primitives.md) section. + +## Paths + +The `path`, `circle` and `square` commands are used to create paths that can be used as the inputs for `builder` commands that can generate complex 3D shapes. They are documented in the [paths](paths.md) section. + +## Text + +The `text` command is used to generate individual words, lines or paragraphs of text, which can then be [filled](builders.md#fill) or [extruded](builders.md#extrude) to create a 3D mesh. The `text` command is documented in the [text](text.md) section. + +## Builders + +The `fill`, `lathe`, `extrude` and `loft` commands turn paths into 3D meshes. They are documented in the [builders](builders.md) section. + +## Constructive Solid Geometry + +The `difference`, `union`, `intersection` and `stencil` commands use boolean operations to merge or subtract shapes from each other to form surfaces that would be hard to model directly. They are documented in the [CSG](csg.md) section. + +## Random Numbers + +The `rnd` and `seed` commands can be used to generate pseudorandom values that are great for procedurally generating natural-looking shapes. The [train example](examples.md#train) uses this approach to create a jumbled layer of coal behind the driver's cab. + +The `rnd` command takes no parameters but returns a random number in the range 0 to 1. It can be used as one of the inputs to a `rotate` or `translate` command, or multiplied by other values as part of an expression to produce random numbers in different ranges: + +```swift +// randomly position a cube between -5 and +5 on the y axis +cube { position 0 (rnd * 10) - 5 0 } +``` + +Each time `rnd` is called it will return a different value. Numbers are returned in a deterministic but non-repeating sequence. Because the sequence is deterministic, it will always produce the same values each time your scene is rendered. + +To alter the random sequence you can use the `seed` command. The `seed` command takes a numeric value as its argument, and this is used to generate all subsequent `rnd` values. The seed value can be any number (positive or negative, integer or fraction), but note that values outside the range 0 to 232 will be wrapped to that range. + +If you are not happy with how some randomly generated geometry looks, try setting the seed to an arbitrary value, and keep tweaking it until you like the result: + +```swift +seed 57 +``` + +You can reset the `seed` at any point within your ShapeScript file, and it will alter the sequence for subsequent `rnd` calls. Like most other commands, `seed` is scoped, so setting the `seed` inside a [group](groups.md) or [block](blocks.md) will only apply to `rnd` calls within that block, and `rnd` commands after the closing `}` will use the previously-specified `seed` value. + +The default starting value for `seed` is zero, so `seed 0` will cause the `rnd` sequence to repeat from the beginning. Remember that the sequence produced from a given seed is always the same, so re-using the same seed value multiple times in your script will result in repetition of the same random sequence. + +## Debugging + +The `debug`, `print` and `assert` commands can be used to understand what's happening in your script and help diagnose problems. These are documented in the [debugging](debugging.md) section. + +## Import + +The `import` command is used to import an external ShapeScript or 3D geometry file. For more details on how this is used, see the [import](import.md) section. + +--- +[Index](index.md) | Next: [Control Flow](control-flow.md) diff --git a/docs/1.7.1/ios/comments.md b/docs/1.7.1/ios/comments.md new file mode 100644 index 00000000..ae20adb0 --- /dev/null +++ b/docs/1.7.1/ios/comments.md @@ -0,0 +1,67 @@ +Comments +--- + +Any content that follows a `//` (double slash) is treated as a comment. Comments can appear at the start of a line, or at the end. The comment terminates at the next line-break. + +Comments can be used to document individual lines of code, or whole blocks. For example: + +```swift +color 1 0 0 // red + +// this code draws a triangle +fill path { + for 0 to 3 { + point 0 1 + rotate 2 / 3 + } +} +``` + +Comments are also useful for temporarily disabling a block of code when debugging a scene. Some editors (such as Xcode) allow you to comment or uncomment multiple lines of code at once by making a multi-line selection and then pressing **Cmd-/** on the keyboard. + +## Block Comments + +Another way to disable a large chunk of code at once is to use a *block comment*. Block comments begin with `/*` and end with `*/`. Anything between these delimiters is considered part of the comment, even if they span multiple lines: + +```swift +/* +// this code is disabled +fill path { + for 0 to 3 { + point 0 1 + rotate 2 / 3 + } +} +*/ +``` + +Block comments can also be useful if you want to place a comment in the middle of a line, for example: + +```swift +define a(b c d) { + b /* + c */ + d +} +``` + +Here we've excluded the `c` parameter from the result, however the code both before and after the comment is unaffected. + +## Nested Comments + +Unlike some other languages, ShapeScript allows block comments to be nested inside each other. This is useful if you want to comment out some code that already contains comments: + +```swift +/* +define foo { + option bar 3 + /* option baz 5 */ + + // return bar + print bar +} +*/ +``` + +**Note:** although nested comments are supported by ShapeScript itself, they may confuse the syntax highlighting in whichever editor you are using. Don't be alarmed if part of the comment appears in the wrong color. + +--- +[Index](index.md) | Next: [Literals](literals.md) diff --git a/docs/1.7.1/ios/control-flow.md b/docs/1.7.1/ios/control-flow.md new file mode 100644 index 00000000..864e06f1 --- /dev/null +++ b/docs/1.7.1/ios/control-flow.md @@ -0,0 +1,175 @@ +Control Flow +--- + +## Loops + +To repeat an instruction (or sequence of instructions) you can use a `for` loop. The simplest form of the for loop takes a [numeric range](expressions.md#ranges), and a block of instructions inside braces. The following loop creates a circle of 5 points (you might use this inside a `path`): + +```swift +for 1 to 5 { + point 0 1 + rotate 2 / 5 +} +``` + +The range `1 to 5` is inclusive of both the start and end values. A range of `0 to 5` would therefore loop *6* times and not 5 as you might expect. + +The loop range does not have to be a literal value, you can use a previously defined symbol or expression instead: + +```swift +define count 5 + +for 1 to count { + point 0 1 + rotate 2 / count +} +``` + +## Loop Index + +If you have used similar loops in other programming languages, you might be wondering why we don't need to use an index variable of some kind to keep track of the loop iteration? + +Symbols defined inside the `{ ... }` block will not persist between loops (see [scope](scope.md) for details), but changes to the world transform will, which is why the `rotate` command doesn't need to reference the index - its effect is cumulative. + +If you *do* need to reference the index inside your loop for some reason, you can define a loop index symbol like this: + +```swift +for i in 1 to count { + point 0 i +} +``` + +This defines a [symbol](symbols.md) called `i` with the value of the current loop iteration. The `i` symbol only exists within the loop body itself and can't be referenced after the loop has ended. + +**Note:** The index symbol does not need to be called `i`, it can be any valid symbol name that you choose. + +If you want to loop in increments greater or less than 1, you can use the optional `step` property: + +```swift +for i in 1 to 5 step 2 { + print i // prints 1, 3, 5 +} + +for i in 0 to 1 step 0.2 { + print i // prints 0, 0.2, 0.4, 0.6, 1 +} +``` + +If not specified, the `step` value defaults to 1. + +## Looping Backwards + +If the end value of a loop is less than the start value, the loop body will normally be skipped, but if you do wish to loop backwards you can achieve this by using a negative step value: + +```swift +for i in 5 to 1 step -1 { + print i // prints 5, 4, 3, 2, 1 +} +``` + +## Looping Over Values + +As well as looping over a numeric range, you can also loop over a tuple of values: + +```swift +define values 1 5 7 9 + +for i in values { + print i // prints 1 5 7 9 +} +``` + +The values can be non-numeric, or even a mix of different types: + +```swift +define values "Mambo" "No." 5 + +for i in values { + print i // prints Mambo No. 5 +} +``` + +**Note:** To use a list of values directly in the loop definition, they must be placed in parentheses: + +```swift +for i in ("parentheses" "are" "required") { + print i +} +``` + +## If-Else + +Sometimes instead of repeating an action multiple times you need to perform it just once, but conditionally, based on some programmatic criteria. For example, your scene might have multiple configurations that you can switch between by setting constants at the top of the file. + +To execute code conditionally, you can use an `if` statement: + +```swift +define showCube true + +if showCube { + cube +} +``` + +The `showCube` constant here is a [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) that can have the value `true` or `false`. The condition for an `if` statement must always be a boolean expression. The body of the `if` statement will only be executed if the condition is true. + +To perform an alternative action for when the condition is false, you can add an `else` clause: + +```swift +if showCube { + cube +} else { + sphere +} +``` + +You can chain multiple conditional statements using the `else if` construct: + +```swift +if showCube { + cube +} else if showSphere { + sphere +} else if showCone { + cone +} else { + print "Nothing to see here!" +} +``` + +## Conditional Defines + +Something you might want to do with an `if` statement is to conditionally define a constant value, for example: + +```swift +define highlighted true + +if highlighted { + define cubeColor red +} else { + define cubeColor white +} + +cube cubeColor +``` + +Unfortunately this won't work, due to the [scope](scope.md) rules. The `cubeColor` symbol is only defined inside the `if` statement blocks themselves, and can't be accessed outside. So how can you set the value of `cubeColor` conditionally? + +The solution is to move the `if` statement *inside* the `define` itself, like this: + +```swift +define highlighted true + +define cubeColor { + if highlighted { + red + } else { + white + } +} + +cube { color cubeColor } +``` + +--- +[Index](index.md) | Next: [Blocks](blocks.md) diff --git a/docs/1.7.1/ios/csg.md b/docs/1.7.1/ios/csg.md new file mode 100644 index 00000000..fb5a64e6 --- /dev/null +++ b/docs/1.7.1/ios/csg.md @@ -0,0 +1,100 @@ +Constructive Solid Geometry +--- + +Constructive Solid Geometry (CSG) is a process by which solid shapes can be combined or subtracted from one another to create shapes that would be hard to model directly. + +There are four basic types of CSG command in ShapeScript: + +## Difference + +The `difference` command takes 2 or more child meshes and subtracts them from one another. In this example, a green-colored cylinder is subtracted from a red cone: + +```swift +difference { + cone { + color 1 0 0 + } + cylinder { + position 0 -0.1 0 + orientation 0.5 + size 0.5 1 + color 0 1 0 + } +} +``` + +The result is shown below on the right. The shape on the left is using a `group` instead of `difference`, so you can see the position of the cylinder prior to subtraction. + +![Cone with a hole](../../images/group-vs-difference.png) + +The subtracted shape is removed from the result, but leaves an impression in the shape it is subtracted from. You can see this in above image, where the hole in the red cone is lined with green coloring from the cylinder. + +## Intersection + +The `intersection` command returns a shape representing the common volume between all input shapes. You can think of it as being like the *AND* function from boolean algebra. + +If we change the `difference` to an `intersection` in the previous code sample, this is the result: + +![Intersection of cone and cylinder](../../images/intersection.png) + +As before, the color is taken from the shape that originally contributed that part of the surface. + +## Union + +The `union` command merges its child meshes into a single shape. You can think of it as being like the *OR* function from boolean algebra. + +For an opaque shape the result is not visually distinguishable from simply using a `group`, but below the surface the `union` will split intersecting polygons and remove internal faces so that only the outer shell remains. + +This can be important for rendering efficiency, as well as producing more predictable results if the mesh is then used for additional CSG operations. + +The effect can be seen by using a translucent color (one with an alpha component less than one). In the image below, the red cones have been set to an alpha opacity of 0.75, with the green cylinders left fully opaque. + +The shape on the left is using a `group`, and the inner surface of the cylinder can clearly be seen through the surface of the cone. The shape on the right is using `union` and so the part of the cylinder inside the cone cannot be seen (because it has been removed). + +![Group vs union](../../images/group-vs-union.png) + +## XOR + +The `xor` command (short for *Exclusive-OR*) combines its child objects using the [even-odd rule](https://en.wikipedia.org/wiki/Even–odd_rule). For example, two overlapping cylinders will produce an eye-shaped hole where the volumes intersect: + +```swift +xor { + cylinder { + color 1 0 0 + } + cylinder { + position 0.5 + color 0 1 0 + } +} +``` + +![Eye-shaped Hole](../../images/xor.png) + +## Stencil + +The `stencil` command retains the shape of its first child, but "paints" the intersecting areas between the child shapes using the material of the other children. This can be used to apply logos or patterns to the surface of a shape. + +In the following example, a red ball has been stenciled with the pattern of a green square: + +```swift +stencil { + // ball + sphere { color 1 0 0 } + // square + cube { + color 0 1 0 + size 0.4 + position 0 0 0.5 + } +} +``` + +![Stenciled ball](../../images/stencil.png) + +**Note:** If the stencil shape has a texture applied to it (see [Materials](materials.md#texture)) then the texture wrapping will match the coordinates of the shape being painted, not the shape that the texture is taken from. + +If the child shapes both have the same color or texture, there will be no visible effect from using stencil. + +--- +[Index](index.md) | Next: [Groups](groups.md) diff --git a/docs/1.7.1/ios/debugging.md b/docs/1.7.1/ios/debugging.md new file mode 100644 index 00000000..98ee5535 --- /dev/null +++ b/docs/1.7.1/ios/debugging.md @@ -0,0 +1,97 @@ +Debugging +--- + +When using the [constructive solid geometry](csg.md) operations it can sometimes be difficult to visualize the individual components that comprise a given shape. In the example below, a moon shape is created by subtracting one cylinder from another: + +```swift +difference { + cylinder { + size 1 0.2 + } + + // cylinder not visible in viewer + cylinder { + position 0.4 + size 1 0.2 + } +} +``` + +![Moon](../../images/moon.png) + +Because the second cylinder is subtracted from the first, we can't actually see it, which means we can't [select it](getting-started.md#debugging-and-selection) to get information about it, or properly understand its shape. To solve this, we can use the `debug` command: + +```swift +difference { + cylinder { + size 1 0.2 + } + + // debug command makes cylinder visible + debug cylinder { + position 0.4 + size 1 0.2 + } +} +``` + +This makes the cylinder visible in the ShapeScript viewer and allows it to be selected like any other shape. + +![Moon debug](../../images/moon-debug.png) + +## Logging + +When creating complex scripts, it can sometimes be difficult to understand what's happening in the code. To help you debug your scripts, you can use the `print` command: + +```swift +print 5 + 6 + +print "some text" + +print someValue +``` + +The `print` command accepts one or more arguments of any type. You can use this to intersperse values and text labels for example: + +```swift +print "width =" width + +print "x:" x "y:" y +``` + +Printed values are displayed in a console area below the scene. The console can be resized and scrolled to show as much text as you need. + +## Assertions + +Rather than merely printing a value, sometimes you want to be certain that it has a particular value (or range of values). You can do that with the `assert` command: + +```swift +assert color = red // no other color will do +``` + +The assert function accepts a single boolean value or expression, and will raise an error if it evaluates to false. + +So when would this be useful? Suppose that you have defined a block, like the [star example](blocks.md#options) and you want a way to specify that it must have at least 4 points and a nonzero radius. You could do that like this: + +```swift +define star { + option radius 1 + assert radius > 0 + option points 5 + assert points >= 4 + path { + for 1 to points { + point 0 -0.5 + rotate 1 / points + point 0 -radius + rotate 1 / points + } + point 0 -0.5 + } +} +``` + +Now, if you (or someone else) tries invoke `star` with invalid options, it will raise a meaningful error instead of just producing broken-looking geometry. + +--- +[Index](index.md) | Next: [Import](import.md) diff --git a/docs/1.7.1/ios/examples.md b/docs/1.7.1/ios/examples.md new file mode 100644 index 00000000..39deab1e --- /dev/null +++ b/docs/1.7.1/ios/examples.md @@ -0,0 +1,55 @@ +Examples +--- + +ShapeScript includes a number of example files that demonstrate various features. You can find these under the `Help > Examples` menu. + +## Ball + +The Ball example demonstrates how to use the [stencil](csg.md#stencil) command to "paint" patterns on a sphere, as well as a [for loop](control-flow.md#loops) to generate a star shape that is then [extruded](builders.md#extrude). + +![Ball](../../images/ball.png) + +## Chessboard + +The Chessboard example demonstrates the use of [for loops](control-flow.md#loops) to duplicate shapes, along with [paths](paths.md), [lathe builders](builders.md#lathe) and [CSG](csg.md) operations. + +![Chessboard](../../images/chessboard.png) + +## Cog + +The Cog example demonstrates procedural generation of a complex [path](paths.md) using a [for loop](control-flow.md#loops), as well as the creation of a custom [block](blocks.md) and the use of the [option](blocks.md#options) command to pass parameters. + +![Cog](../../images/cog.png) + +## Earth + +The Earth example demonstrates use of the [texture](materials.md#texture) command to turn a simple sphere into a model of the globe. + +![Earth](../../images/earth.png) + +## Icosahedron + +The Icosahedron demonstrates the use of the [mesh command](meshes.md) along with [structured data](literals.md#structured-data) and [custom functions](functions.md#custom-functions) to create a complex shape that cannot easily be derived from the built-in primitives. + +![Icosahedron](../../images/icosahedron.png) + +## Spirals + +The Spirals example demonstrates using the [extrude](builders.md#extrude) command to create a spiral. This example also demonstrates the use of [for loops](control-flow.md#loops) and user-defined [options](blocks.md#options). + +![Spirals](../../images/spirals.png) + +## Spring + +The Spring example demonstrates how to use the [loft](builders.md#loft) command and a [for loop](control-flow.md#loops) to create a coiled spring shape. + +![Spring](../../images/spring.png) + +## Train + +The Train demonstrates a combination of modeling techniques, including using the [rnd](commands.md#random-numbers) command to generate pseudo-random coal. + +![Train](../../images/train.png) + +--- +[Index](index.md) | Next: [Glossary](glossary.md) diff --git a/docs/1.7.1/ios/export.md b/docs/1.7.1/ios/export.md new file mode 100644 index 00000000..ebd32163 --- /dev/null +++ b/docs/1.7.1/ios/export.md @@ -0,0 +1,78 @@ +Export +--- + +Once you've finished crafting your 3D scene, you'll probably want to *do something* with it. For that you will need to use the *Export* feature. + +**Export is a paid upgrade that can be unlocked via in-app purchase in the [ShapeScript iOS App](https://apps.apple.com/app/id1606439346). Export is not available in the free ShapesScript Viewer.** + +To export your scene, select the export menu in the top-right of the window to unlock the export functionality. Once unlocked, this menu will display the export options. + +**Note:** If the `Export Image/Model` menu is grayed-out, it is most likely because your scene is still loading. Wait for the loading spinner in the top-left of the ShapeScript window to finish before trying to export. + +![Loading](../../images/loading-ios.png) + +## Export Formats + +ShapeScript can export your scene in a variety of model file formats, selectable from the export menu: + +Extension | File Type | Supports All Features +:-------------------- | :------------------------------------------------|:------------------------------ +abc | Alembic | No +dae | Collada DAE | Yes +obj | Wavefront Object | No +scn / scnz | SceneKit Scene Document | Yes +usd / usdz | Universal Scene Description | No +ply | Polygon File Format | No +stl | Stereolithography | No + +
+ +**Note:** Not all formats support all features of ShapeScript scenes, so you may need to experiment. In general, DAE is the most reliable, widely-supported format to use. + +Some model formats do not support embedding geometry and textures or materials in a single file; In this case, ShapeScript will export a folder containing the model and associated assets as separate files. + +Exported models can be used in a variety of ways: + +## Games and AR + +Models exported from ShapeScript are well-suited to use in realtime 3D because the `detail` command gives you fine control over the triangle count. For realtime use you should generally set the detail level as low as you can get away with. + +You can import DAE files into a game development tool like Unity, or use USD(Z) files with Apple's SceneKit and RealityKit frameworks in Xcode. + +## 3D Printing + +ShapeScript can export models in Stereolithography (STL) format, used by many 3D printing applications. + +When exporting for 3D printing, you will usually want to avoid having internal geometry inside the outer surface of your model. A good way to do this is to use the [union](csg.md#union) command to combine all the parts of your model into a single shape, eliminating internal faces. + +ShapeScript scenes use the "Y-up" convention, where the Y-axis points up and the Z-axis points out from the screen. Some popular 3D printing applications such as [Cura](https://ultimaker.com/software/ultimaker-cura) use the "Z-up" convention instead. Check the "Convert to Z-Up" option in the export menu to export your model in this orientation. + +## Image Formats + +In addition to 3D model formats, ShapeScript can also export 2D images. By default, images will be captured using the current camera, but you can select a different [camera view](cameras.md) from the export window. + +The following image formats are supported: + +Extension | File Type | Supports Transparency +:---------------------| :-------------------------------------------------|:------------------------------ +png | Portable Network Graphics | Yes +jpg / jpeg | Joint Photographic Experts Group | No + +
+ +If you aren't sure which format to use, the PNG format is a good all-rounder, with lossless compression and transparency support. + +## Image Options + +The size of the exported image defaults to the current window size at the current display resolution. You can override this size in your script file by adding `width` and/or `height` options to your [custom camera](cameras.md#pixel-dimensions), or by tapping on the size label in the export menu and selecting a different size: + +![Export size](../../images/export-size-ios.png) + +Images are exported with a transparent background by default if the selected format supports it, or white otherwise. To change the background color or set a background image, you can use the [background command](commands.md#background). + +When exporting an image (or exporting a model for non-realtime use), you may wish to use the `detail` command to increase the detail level. A detail level of 100 should be good enough for even a very large or high-resolution image, but this may take a long time to generate for a complex model. + +**Note:** Although ShapeScript can export images, for best results you should export as a 3D model and then import that into a [ray tracing](https://en.wikipedia.org/wiki/Ray_tracing_(graphics)) program that provides fine-grained control over scene lighting and camera placement. + +--- +[Index](index.md) | Next: [Examples](examples.md) diff --git a/docs/1.7.1/ios/expressions.md b/docs/1.7.1/ios/expressions.md new file mode 100644 index 00000000..033fe309 --- /dev/null +++ b/docs/1.7.1/ios/expressions.md @@ -0,0 +1,313 @@ +Expressions +--- + +Rather than pre-calculating the sizes and positions of your shapes, you can get ShapeScript to compute the values for you using *expressions*. + +Expressions are formed by combining [literal values](literals.md), [symbols](symbols.md) or [functions](functions.md) with *operators*. + + +## Operators + +Operators are used in conjunction with individual values to perform calculations: + +```swift +5 + 3 * 4 +``` + +ShapeScript supports all the standard [infix](https://en.wikipedia.org/wiki/Infix_notation) arithmetic operators: + +Symbol | Name | Function +:------------- | :-------------------- | :-------------------------------------------------------------------- ++ | plus | Adds the left and right values +‐ | minus | Subtracts the right value from the left value +* | times | Multiplies the left value by the right value +/ | divide | Divides the left value by the right value +% | modulo | Remainder of dividing the left value by the right value + +
+ +Unary + and - are also supported: + +```swift +-5 * +7 +``` + +Operator precedence follows the standard [BODMAS](https://en.wikipedia.org/wiki/Order_of_operations#Mnemonics) convention, and you can use parentheses to override the order of evaluation: + +```swift +(5 + 3) * 4 +``` + +Because spaces are used as delimiters in [vector literals](literals.md#vectors-and-tuples), you need to take care with the spacing around operators to avoid ambiguity. Specifically, unary + and - must not have a space after them, and ordinary infix operators should have balanced space around them. + +For example, these expressions would both evaluate to a single number with the value 4: + +```swift +5 - 1 +5-1 +``` + +Whereas this expression would be interpreted as a 2D vector of 5 and -1: + +```swift +5 -1 +``` + +## Equality and Comparison + +ShapeScript includes the following equality and comparison operators, which can be used in [conditional logic](control-flow.md#if-else): + +Symbol | Name | Function +:------------- | :-------------------- |:-------------------------------------- += | equal | Compares two values and returns `true` if they are equal +<> | not equal | Compares two values and returns `false` if they are equal +< | less than | Returns `true` if the left value is less than the value on the right +<= | less than or equal | Returns `true` if the left value is less than or equal to the right +> | greater than | Returns `true` if the left value is greater than the value on the right +>= | greater than or equal | Returns `true` if the left value is greater than or equal to the right + +
+ +**Note:** You may have used other languages where `=` is written as `==`. This is generally because in such languages the `=` operator is used for assignment, and re-using the same symbol would cause ambiguity. This is not a problem in ShapeScript. + +While these operators are typically used with numeric inputs, the *output* is a boolean value (`true` or `false`). These values are most commonly used in conjunction with with the `if/else` control flow statement. For example: + +```swift +if rnd > 0.5 { + print "heads" +} else { + print "tails" +} +``` + +But they can also be assigned to a symbol and passed around: + +```swift +define averageColor (color.red + color.green + color.blue) / 3 +define isBrightColor averageColor >= 0.5 +print isBrightColor // prints true or false +``` + +## Linear Algebra + +As well as operating on individual numbers, some operators can be used with [vectors or tuples](literals.md#vectors-and-tuples). To multiply or divide a tuple of numbers by a scalar value you can use: + +```swift +define numbers (1 2 3 -4) +print numbers * 2 // prints 2 4 6 -8 +``` + +You can also multiply two tuples: + +```swift +define left (1 2 3) +define right (1 -2 3) +print left * right // prints 1 -4 9 +``` + +Note that is a simple member-wise multiplication of the numbers. For other types of vector multiplication such as the dot or cross product see the [functions section](functions.md#linear-algebra). + +If the tuples have different lengths, the result will be truncated to the shorter of the two: + +```swift +define left (1 2 3) +define right (1 -2) +print left * right // prints 1 -4 +``` + +You can also add or subtract two lists of numbers together: + +```swift +define left (1 2 3) +define right (1 -2 3) +print left + right // prints 2 0 6 +``` + +Unlike with multiplication or division, adding or subtracting a shorter tuple from a longer one will preserve the length of the left side: + +```swift +define left (1 2 3) +define right (1 -2) +print left + right // prints 2 0 3 +``` + +Adding a longer tuple to a shorter one will not widen the result however: + +```swift +define left (1 2 3) +define right (1 -2 3 4) +print left + right // prints 2 0 3 +``` + +## Boolean Algebra + +Along with the standard arithmetic operators, ShapeScript also has [boolean operators](https://en.wikipedia.org/wiki/Boolean_algebra) for implementing logical operations. + +Not to be confused with the [boolean geometry](csg.md) functions for working with 3D solids, ShapeScript's boolean operators work with `true` or `false` values, and are predominantly used in conjunction with `if/else` control flow statements. + +ShapeScript supports the common boolean operators: + +Operator | Function +:------------- | :-------------------- +and | Compares two values and returns `true` if they are both true +or | Compares two values and returns `true` if either one is true +not | Returns `false` if the expression to the right is true, and `true` if it's false + +
+ +Unlike some languages, ShapeScript's boolean operators are implemented as keywords rather than symbols like `&&` or `||`, so control flow statements read more like sentences: + +```swift +if a and b { + print "both a and b were true" +} +``` + +These can be combined into more complex expressions, and used in conjunction with parentheses for disambiguation: + +```swift +if (not a) and (b or c) { + print "a was false and either b or c were true" +} +``` + +## Members + +Compound values like [vectors and tuples](literals.md#vectors-and-tuples) can be decomposed by using the *dot* operator to access individual components: + +```swift +define vector 0.5 0.2 0.4 +define yComponent vector.y +print yComponent 0.2 +``` + +Like other operators, the dot operator can be used as part of a larger expression: + +```swift +color 1 0.5 0.2 +define averageColor (color.red + color.green + color.blue) / 3 +print averageColor // prints 0.5667 +``` + +For strings, you can use the `lines`, `words` and `characters` members: + +```swift +define sentence "The quick brown fox" +for word in sentence { + print word // prints each word on a new line +} +``` + +For [paths](paths.md) you can access the `bounds` and `points` members. For each point you can access the `position`, `isCurved` and `color`: + +```swift +// Print the points in a circle +for point in circle.points { + print "position: " point.position ", isCurved: " point.isCurved +} +``` + +For [meshes](meshes.md) you can access the `name`, `bounds` and `polygons` members: + +```swift +print cube.bounds.size // prints 1 1 1 +print cube.polygons.count // prints 6 +``` + +For [polygons](meshes.md#polygons-and-points) you can get the `bounds` or `center`, or use `points` to access the individual vertices. For points you can access the `position` and `color`: + +```swift +define triangle polygon { + color red + point 0 0 + color green + point 1 0 + color blue + point 1 1 +} + +// Print the vertex positions and colors +for point in triangle.points { + print "position: " point.position ", color: " point.color +} +``` + +For more information about the members that can be accessed on various data types, see [structured data](literals.md#structured-data). + + +## Ranges + +Another type of expression you can create is a *range* expression. This consists of two numeric values separated by a `to` keyword: + +```swift +1 to 5 +``` + +Ranges are mostly used in [for loops](control-flow.md#loops): + +```swift +for i in 1 to 5 { + print i +} +``` + +But they can also be assigned to a [symbol](symbols.md) using the `define` command, and then used later: + +```swift +define loops 1 to 5 + +for i in loops { + print i // prints 1, 2, 3, 4, 5 +} +``` + +**Note:** Ranges are inclusive of both the start and end values, so a loop from `0 to 5` would loop *6* times and not 5 as you might expect. + +Range values can be fractional and/or negative: + +```swift +for i in 0.2 to 2.2 { + print i // prints 0.2, 1.2, 2.2 +} + +for i in -3 to -1 { + print i // prints -3, -2, -1 +} +``` + +Ranges may also include an optional `step` value to control how the range will be enumerated: + +```swift +for i in 1 to 5 step 2 { + print i // prints 1, 3, 5 +} + +for i in 0 to 1 step 0.2 { + print i // prints 0, 0.2, 0.4, 0.6, 1 +} +``` + +The step value for an existing range can be set or overridden later: + +```swift +define loops 1 to 5 step 3 + +for i in loops { + print i // prints 1, 4 +} + +for i in loops step 2 { + print i // prints 1, 3, 5 +} +``` + +A negative `step` can be used to create a [backwards loop](control-flow.md#looping-backwards): + +```swift +for i in 5 to 1 step -1 { + print i // prints 5, 4, 3, 2, 1 +} +``` + +--- +[Index](index.md) | Next: [Functions](functions.md) diff --git a/docs/1.7.1/ios/functions.md b/docs/1.7.1/ios/functions.md new file mode 100644 index 00000000..14efaa8d --- /dev/null +++ b/docs/1.7.1/ios/functions.md @@ -0,0 +1,296 @@ +Functions +--- + +Functions consist of a name followed by one or more values. They perform an operation on their input values and return the result. + +Functions can be used inside expressions, and can accept expressions as inputs. Unlike [operators](expressions.md#operators), functions have no implicit precedence, so parentheses may be needed to avoid ambiguity. + +In the following example, it's not clear if `y` is intended as a second argument to the `cos` function, or as the second argument to the translate command. Only the latter would technically be valid, since `cos` only accepts a single argument, but ShapeScript requires you to be explicit, and will treat this as an error: + +```swift +translate cos x y +``` + +The error can be resolved by using parentheses to group the `cos` function with its argument: + +```swift +translate (cos x) y +``` + +Lisp programmers will find this syntax quite familiar, but if you have used C-like programming languages it may seem a little strange to put the parentheses around the function name and its arguments instead of just the arguments. If you prefer, you can use a C-like syntax instead: + +```swift +translate cos(x) y +``` + +Either approach is acceptable in ShapeScript. + +## Arithmetic + +In addition to the standard arithmetic [operators](expressions.md#operators), ShapeScript also includes a number of built-in arithmetic *functions*: + +The `round` function is used to round a number to the nearest integer (whole number): + +```swift +round 3.2 // returns 3 +round 3.9 // returns 4 +round 3.5 // returns 4 +``` + +The `floor` function is similar, but always rounds down: + +```swift +floor 3.2 // returns 3 +floor 3.9 // returns 3 +``` + +The `ceil` function always rounds up: + +```swift +ceil 3.2 // returns 4 +ceil 3.9 // returns 4 +``` + +The `abs` function returns the magnitude of a number, ignoring the sign: + +```swift +abs 4.5 // returns 4.5 +abs -51 // returns 51 +``` + +The `sign` function (not to be confused with [sin](#trigonometry)) returns `1`, `-1` or `0` depending on the sign of the input: + +```swift +sign 4.5 // returns 1 +sign -51 // returns -1 +sign 0 // returns 0 +``` + +The `sqrt` function returns the square root of a value: + +```swift +sqrt 4 // returns 2 +sqrt 2 // returns 1.414 +``` + +The `pow` function takes *two* parameters, and return the first value raised to the power of the second: + +```swift +pow 2 4 // returns 16 +pow 3 2 // returns 9 +pow 4 0.5 // returns 2 +``` + +The `min` function returns the lower of two or more values: + +```swift +min 2 4 // returns 2 +min 5 0 -5.1 // returns -5.1 +``` + +The `max` function returns the higher of two or more values: + +```swift +max 2 4 // returns 4 +max 5 0 -5.1 // returns 5 +``` + +## Linear Algebra + +ShapeScript also includes functions for operating on [vectors](literals.md#vectors-and-tuples): + +The `dot` function is used to calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of a pair of vectors: + +```swift +define up (0 1) +define right (1 0) +print dot(up right) // returns 0 +``` + +The `cross` function calculates the [cross product](https://en.wikipedia.org/wiki/Cross_product): + +```swift +define up (0 1) +define right (1 0) +print cross(up right) // returns 0 0 -1 +``` + +The `length` function calculates the magnitude of a vector (also known as the [norm](https://en.wikipedia.org/wiki/Euclidean_space#Euclidean_norm)): + +```swift +define v (3 4) +print length(v) // returns 5 +``` + +The `normalize` function computes a [unit vector](https://en.wikipedia.org/wiki/Unit_vector) from an arbitrary vector by dividing each element by the vector length: + +```swift +define v (3 4) +print normalize(v) // returns 0.6 0.8 +``` + +## Trigonometry + +For the most part, you can avoid the need for trigonometry in ShapeScript by using the built-in [transform commands](transforms.md#relative-transforms) to manipulate geometry rather than manually calculating the positions of vertices. + +But sometimes you may wish to do something more complex (e.g. generating a path in the shape of a sign wave) that can only be achieved through explicit calculations, and to support that, ShapeScript provides a standard suite of trigonometric functions. + +While ShapeScript's [transform](transforms.md#orientation) commands expect values in [half-turns](https://en.wikipedia.org/wiki/Turn_(angle)), the trigonometric functions all use [radians](https://en.wikipedia.org/wiki/Radian). + +For example, the `sin` (sine) function takes a radian representation of an angle and returns a ratio value of that angle. In this case 0.524 radians returns 0.5 or 1/2 - an angle of 30 degrees: + +```swift +sin 0.524 // returns 0.5 +``` + +The `acos` (arc cosine) function takes a ratio representation of an angle and returns a radians value of that angle. In this case 1/2 or 0.5 returns 1.047 radians - equivalent to an angle of 60 degrees: + +```swift +acos 0.5 // return 1.047 +``` + +The `cos` (cosine), `sin` (sine), and `tan` (tangent) functions all take a radians value and return a ratio value, and the `asin` (arc sine), `acos` (arc cosine), and `atan` (arc tangent) functions all take a ratio value and return a radians value. + +Using `atan` to calculate the angle of a vector is problematic because the result that it returns can be ambiguous. You need to take the vector quadrant into account, as well as the ratio of the X and Y components. + +The `atan2` function works like `atan`, but instead of a single tangent value, it accepts separate Y and X inputs and returns the angle of the vector that they describe. The resultant angle correctly takes the vector quadrant into account. + +```swift +atan2 1 -1 // returns a radian angle of the vector Y: 1, X: -1 +``` + +To convert an angle in radians to a ShapeScript half-turn value, divide it by the 'pi' constant: + +```swift +define angle acos(0.5) // returns 1.047 radians (60 degrees) +rotate angle / pi // return 0.333 (1.047 / 3.141) +``` + +To convert a ShapeScript half-turn value to radians, multiply it by `pi`. + +```swift +cube { + orientation 0.5 + print orientation.roll * pi // prints 1.571 (0.5 * pi) +} +``` + +Angular conversion formulae: + +Conversion | Formula +:---------------------| :-------------------------- +Degrees to radians | radians = degrees / 180 * pi +Radians to degrees | degrees = radians / pi * 180 +Degrees to half-turns | halfturns = degrees / 180 +Half-turns to degrees | degrees = halfturns * 180 +Radians to half-turns | halfturns = degrees / pi +Half-turns to radians | radians = halfturns * pi + +
+ +Common values: + +Angle in degrees | Angle in radians | Angle in half-turns +:--------------- | :--------------- | :------------------ +0 | 0 | 0 +30 | pi / 6 (0.524) | 1 / 6 (0.167) +45 | pi / 4 (0.785) | 1 / 4 (0.25) +60 | pi / 3 (1.047) | 1 / 3 (0.333) +90 | pi / 2 (1.57) | 1 / 2 (0.5) +180 | pi (3.142) | 1 + +
+ +## Strings + +ShapeScript includes a number of functions for manipulating strings. + +The `join` function concatenates two or more strings together, with an optional separator: + +```swift +define words "Hello" "World!" +define greeting join words ", " +print greeting // prints "Hello, World!" +``` + +If you don't want a delimiter, just pass an empty string as the second argument to join: + +```swift +define words "Hello" "World!" +define greeting join(words "") +print greeting // prints "HelloWorld!" +``` + +The `split` function breaks a string up into a tuple of substrings by splitting it at a specified delimiter: + +```swift +define input "comma,delimited,string" +define elements split input "," +print elements.second // prints "delimited" +``` + +The `trim` function removes any leading or trailing [white space](https://en.wikipedia.org/wiki/Whitespace_character) characters from a string: + +```swift +define input "comma, delimited , string,with ,spaces" +define elements split input "," +print elements.second // prints " delimited " +print trim elements.second // prints just "delimited" without spaces +``` + +## Functions and Expressions + +Expressions can be passed as function arguments, for example: + +```swift +sin pi / 2 // returns 1 +``` + +Which, thanks to precedence rules, is equivalent to: + +```swift +sin(pi / 2) // returns 1 +``` + +You can also use function calls *inside* an expression, for example: + +```swift +print (sqrt 9) + (sqrt 9) // prints 6 +``` + +Or the equivalent form of: + +```swift +print sqrt(9) + sqrt(9) // also prints 6 +``` + +**Note:** When used inside an expression, parentheses around the function (or just its arguments) are required. + +## Custom Functions + +You can define your own functions using the `define` command. A function definition consists of a function name followed by a list of parameter names in parentheses: + +```swift +define sum(a b) { + a + b +} + +define degreesToRadians(degrees) { + degrees / 180 * pi +} +``` + +Like [blocks](blocks.md), functions can refer to constant values or other functions defined in their containing [scope](scope.md): + +```swift +define epsilon 0.0001 + +define almostEqual(a b) { + abs(a - b) < epsilon +} +``` + +Unlike [block options](blocks.md#options), function inputs do not have default values. Calling a function without passing a value for every input will result in an error. + +--- +[Index](index.md) | Next: [Commands](commands.md) diff --git a/docs/1.7.1/ios/getting-started.md b/docs/1.7.1/ios/getting-started.md new file mode 100644 index 00000000..4c576b25 --- /dev/null +++ b/docs/1.7.1/ios/getting-started.md @@ -0,0 +1,116 @@ +Getting Started +--- + +ShapeScript Viewer for iOS is designed for opening and editing 3D models and scenes that use the ShapeScript `.shape` file format. + +If you do not have any existing ShapeScript files, you can create one using the `+` button. You will be prompted to select a name and location for your new file - you can call it anything you want, but be sure to retain the `.shape` file extension. + +![New file modal](../../images/new-file-ios.png) + +After pressing OK you should see a new screen appear containing a cube, sphere and cone. These are the default contents for a new file (don't worry, you can replace them with your own shapes). + +![New document window](../../images/new-document-ios-1.6.4.png) + +## Viewing + +Drag your finger inside the window and you will notice that the three shapes appear to rotate. + +You are not actually moving the shapes themselves, but rotating the camera around them. To return to the original orientation, tap the camera icon in the top-right and select `Reset View`. + +To help you orient yourself, you can enable the `Show Axes` option in the camera menu, which displays the direction of the X, Y and Z axes. + +![Show axes](../../images/show-axes-ios-1.6.4.png) + +For more information about navigating around the scene, see [Camera Control](camera-control.md). + +## Editing + +To edit your `.shape` file, tap the pencil icon in the top-right of the screen. This will open the file inside the integrated source editor: + +![Integrated source editor](../../images/source-editor-ios.png) + +Changes made to the model in the source editor will be reflected immediately in the scene (although you may need to close the source view to see them!). + +In addition to the built-in editor, ShapeScript also supports the use of external text editors such as [Runestone](https://apps.apple.com/app/runestone-text-editor/id1548193893) or [Subtext](https://apps.apple.com/app/subtext/id1606625287). + +ShapeScript files are just ordinary text documents, so they can be opened in any program that supports editing text. No third-party editors currently support ShapeScript syntax directly, but if you set your editor to treat `.shape` files as another C-like language (e.g. C, Swift or Java) then it should do an acceptable job of syntax coloring and indenting. Some editors (such as Runestone) will make this association automatically, which will save you having to do it each time you edit a ShapeScript file. + +If you are using an iPad, you can use the [Split View](https://support.apple.com/en-gb/guide/ipad/ipad08c9970c/ipados) feature to show both ShapeScript Viewer and your text editor app on screen simultaneously: + +![Split View](../../images/split-view-ios-1.6.4.png) + +**Note:** Not all iOS text editors support *live editing* (where changes in the text are reflected automatically in ShapeScript as you make them). You may find you have to explicitly save or close the document in your editor for changes to take effect. External editors may also not immediately pick up changes you make using the built-in source editor, so be careful when switching between them. + +## File Structure + +The first line of the default `.shape` document looks like this: + +```swift +// ShapeScript document +``` + +Lines beginning with `//` are comments, and are ignored by the parser. You can use these comments to document what the different parts of your file are doing. + +The next line is: + +```swift +detail 32 +``` + +This controls the level of detail used for shapes in the file. The shapes are made out of flat polygons, so it is not possible for curved edges to be represented exactly. When you define a sphere or other curved shape, the `detail` value is used to decide how many polygons will be used. + +Next we have the following: + +```swift +cube { + position -1.5 + color 1 0 0 +} +``` + +These three lines (ignoring the closing `}`) represent three distinct instructions, although they are grouped together because they all relate to a single shape. White space (such as spaces, tabs, or blank lines) is mostly ignored, however each new instruction must be placed on a new line. + +The first instruction, `cube`, creates the cube shape. Note the `{ ... }` braces after `cube`. Content inside braces relates to the instruction that precedes them. In this case, the braces contain instructions that set the `position` and `color` of the cube. + +The line `position -1.5` means "position the shape 1.5 spacial units to the left of the origin". The *origin* is the center of the world, and is the default position at which new shapes will appear. + +**Note:** The spacial units ShapeScript uses are arbitrary, and can represent anything that you wish them to: a centimeter, a meter, an inch, a mile, etc. Try to choose an appropriate scale for your scene so that you avoid having to work with very large numbers (thousands of units), or very small ones (thousandths of a unit). That will help to avoid rendering precision issues, as well as making your scripts easier to work with. + +The `position` keyword is followed by up to 3 numbers representing offsets along the X, Y, and Z axes respectively. If values are omitted, they are assumed to be zero, so a position o-f `-1.5` is equivalent to `-1.5 0 0`. This is covered in detail in the [transforms](transforms.md#position) section. + +The next line, `color 1 0 0` sets the current color. The three numbers after the keyword represent the red, green, and blue color channels, each with a range from 0 to 1. Because the red channel is 1 and the other channels are 0, the resultant color will be pure red. + +As with `position`, you can omit values for the `color`, but the behavior is slightly different: If you only specify one value then that will be used for all three color channels. You can optionally specify a fourth "alpha" value to control transparency. This is covered in detail in the [materials](materials.md#color) section. + +The groups of instructions after the cube are similar, but define a sphere and cone respectively. + +## Next Steps + +Change the `color 1 0 0` instruction to `color 1 1 0`, and then save the file. If you switch back to the ShapeScript app you should now see that the red cube has changed to yellow. ShapeScript tracks changes to any open files and will automatically update the view whenever they are saved. + +![Yellow cube](../../images/yellow-cube-ios-1.6.4.png) + +Try tweaking the values for the `position` and `color` instructions and observing the effects. You could also try adding a `size` option, which works like the others, but controls the *size* of the shape. + +If you make a mistake, you will see a screen like this: + +![Error screen](../../images/error-screen-ios-1.6.4.png) + +There is no cause for alarm if you see this screen. Just fix your mistake and save again to clear the error. If you aren't sure what you did, and the error message doesn't help, use undo feature in your editor to backtrack to a working state and try again. + +When you are comfortable with manipulating the default shapes, try deleting them and adding new ones of your own. + +**Note:** Many text editors save automatically every few seconds, so you may find that ShapeScript applies your changes as you type, even if you haven't explicitly saved. If you are in the middle of typing a new command when the automatic refresh takes place, ShapeScript may display an error, just as if you had manually saved an incomplete file. Errors that appear while you are still typing can be safely ignored. + +## Debugging and Selection + +To get information about your scene (such as polygon count and overall dimensions), tap the info button in the top-right corder of the screen. + +To get info about a particular shape in the scene, tap on it in the viewer to select it then press the info button. This will also tell you which line in the `.shape` file defines that component. Tap outside of the shape to deselect it again. + +![Object info](../../images/object-info-ios-1.6.4.png) + +**Note:** If you are having trouble identifying a particular shape from your `.shape` file, you can use the [debug command](debugging.md) to highlight it in the viewer. + +--- +[Index](index.md) | Next: [Camera Control](camera-control.md) diff --git a/docs/1.7.1/ios/glossary.md b/docs/1.7.1/ios/glossary.md new file mode 100644 index 00000000..36bd57f7 --- /dev/null +++ b/docs/1.7.1/ios/glossary.md @@ -0,0 +1,29 @@ +Glossary +--- + +Term | Definition +:--------------------| :-------------------------- +Block | A group of commands inside braces (sometimes synonymous with Scope) +Builder | A command for creating a 3D mesh out of one or more 2D paths +Color | An RGBA color to be applied to a mesh or vertex +CSG | Constructive Solid Geometry - adding or subtracting meshes to make new shapes +Function | A named, parameterized block that returns a value +Geometry | A collective term for 2D or 3D shapes produced using ShapeScript +Group | A group of paths or meshes. May also contain nested groups +Half-turn | The unit used to specify angles or rotations in ShapeScript +Material | A color or texture +Mesh | A 3D shape made of polygons +Member | A single component of a compound value like a vector or tuple +Path | A line or curve in 3D space. May be open-ended or closed +Primitive | A simple, built-in shape like a cube or sphere +Range | A numeric range with a start, end and step value +Scope | A section of code with its own symbol namespace +Symbol | A named constant value or function +Texture | An image map to be wrapped around a mesh +Transform | A combined translation, rotation and scale +Translation | A relative or absolute position or offset in 3D space +Tuple | A list of values that can be accessed with .first, .second, etc. +Vector | A position, direction or distance in 3D space + +--- +[Index](index.md) \ No newline at end of file diff --git a/docs/1.7.1/ios/groups.md b/docs/1.7.1/ios/groups.md new file mode 100644 index 00000000..20ef9505 --- /dev/null +++ b/docs/1.7.1/ios/groups.md @@ -0,0 +1,42 @@ +Groups +--- + +Complex 3D shapes often follow a natural hierarchy - a car has wheels, or a torso has arms and legs, etc. You can create a hierarchy in ShapeScript by using the `group` command: + +```swift +group { + position -0.5 0 0 + sphere { + position 0 0 0 + } + cylinder { + position 1 0 0 + } +} +``` + +Like other 3D shapes, a group has a `position` and `orientation` in 3D space, but it can also contain child shapes which inherit its coordinate system; if you move the group, its children move with it. The `position`, `orientation` and `size` properties of the children will be treated as relative to those of the containing group. + +You can override the current [options](options.md) (material, detail, etc.) at any point inside a group and only the subsequent children will be affected. You can also use [relative transform](transforms.md#relative-transforms) commands to manipulate the position of subsequent shapes inside the group: + +```swift +group { + color 1 0 0 + sphere + translate 1 + color 0 1 0 + cylinder + translate 1 + rotate 0.1 + scale 0.5 + color 0 0 1 + cube +} +``` + +![Group](../../images/group.png) + +**Note:** Changes made to properties such as transform or material are [scoped](scope.md) to the group, and will not carry beyond the closing `}`. + +--- +[Index](index.md) | Next: [Lights](lights.md) diff --git a/docs/1.7.1/ios/import.md b/docs/1.7.1/ios/import.md new file mode 100644 index 00000000..225eca95 --- /dev/null +++ b/docs/1.7.1/ios/import.md @@ -0,0 +1,78 @@ +Import +--- + +## Scripts + +ShapeScript files can get quite large for complicated scenes, and you may find that there are common shapes that you wish to across multiple ShapeScript files. The `import` command can help with both of these problems: + +```swift +import "MyShape.shape" +``` + +Here we've used `import` to load an external ShapeScript file and evaluate it inside the calling script. Any symbols that are defined in the imported file will become available inside the [scope](scope.md) in which it is loaded, and any geometry created by the imported file will be displayed. + +You can import the same file several times in the same script, and import statements can appear inside [loops](control-flow.md#loops), [if statements](control-flow.md#if-else) or [blocks](blocks.md). If you don't want the geometry inside an imported file to be displayed immediately, you can place the import inside a [define](symbols.md) statement: + +```swift +define ball { + import "Ball.shape" +} +``` + +In this way, the loaded shape is bound to a symbol of your choice, and can be used a later point in the script. This approach also prevents any [symbols](symbols.md) defined in the imported script from leaking outside into the calling script's global [scope](scope.md). + +**Note:** As with textures, the first time you try to import a file you may see an [access permission](materials.md#access-permission) warning. + +## Models + +The `import` command is not limited to loading `.shape` files. It can load models in many of the [export formats](export.md) supported by ShapeScript. Imported models can be used just like imported script files: + +```swift +define rocket { + texture "Rocket.png" + import "Rocket.obj" +} +``` + +Depending on the format, imported models may include their own [materials](materials.md). Uncolored / untextured models will inherit the current ShapeScript material properties. + +## Text and Data + +In addition to scripts and standard model formats, text files can be imported as a [string](literals.md#strings) value. + +```swift +define text { + import "Text.txt" +} +``` + +Imported strings can then be displayed directly, or further processed using ShapeScript's [string functions](functions.md#strings): + +```swift +for line in text.lines { + print (trim line) +} +``` + +As well as plain text, ShapeScript also supports importing JSON files as [structured data](literals.md#structured-data): + +```swift +define data { + import "Data.json" +} +``` + +## Dynamic Imports + +It can sometimes be useful to generate the name of an imported file dynamically. For example if you have multiple numbered files to import, you might want to generate the names programatically. + +You can do this using the [text interpolation](text.md#interpolation) feature. The following code, for example, will load the files "Shape1.shape", "Shape2.shape"... up to "Shape10.shape": + +```swift +for n in 1 to 10 { + import "Shape" n ".shape" +} +``` + +--- +[Index](index.md) | Next: [Export](examples.md) diff --git a/docs/1.7.1/ios/index.md b/docs/1.7.1/ios/index.md new file mode 100644 index 00000000..af47eada --- /dev/null +++ b/docs/1.7.1/ios/index.md @@ -0,0 +1,165 @@ +ShapeScript Help +--- + +- [Getting Started](getting-started.md) + - [Viewing](getting-started.md#viewing) + - [Editing](getting-started.md#editing) + - [File Structure](getting-started.md#file-structure) + - [Next Steps](getting-started.md#next-steps) + - [Debugging and Selection](getting-started.md#debugging-and-selection) +- [Camera Control](camera-control.md) + - [Camera Selection](camera-control.md#camera-selection) + - [Mouse Control](camera-control.md#mouse-control) + - [Trackpad Control](camera-control.md#trackpad-control) + - [Copy Settings](camera-control.md#copy-settings) +- Geometry + - [Primitives](primitives.md) + - [Cube](primitives.md#cube) + - [Sphere](primitives.md#sphere) + - [Cylinder](primitives.md#cylinder) + - [Cone](primitives.md#cone) + - [Options](options.md) + - [Name](options.md#name) + - [Detail](options.md#detail) + - [Smoothing](options.md#smoothing) + - [Transform](options.md#transform) + - [Material](options.md#material) + - [Materials](materials.md) + - [Color](materials.md#color) + - [Texture](materials.md#texture) + - [Opacity](materials.md#opacity) + - [Transforms](transforms.md) + - [Position](transforms.md#position) + - [Orientation](transforms.md#orientation) + - [Size](transforms.md#size) + - [Relative Transforms](transforms.md#relative-transforms) + - [Bounds](bounds.md) + - [Mesh Bounds](bounds.md#mesh-bounds) + - [Path Bounds](bounds.md#path-bounds) + - [Bounds Members](bounds.md#bounds-members) + - [Meshes](meshes.md) + - [Polygons and Points](meshes.md#polygons-and-points) + - [Watertightness](meshes.md#watertightness) + - [Winding order](meshes.md#winding-order) + - [Procedural Meshes](meshes.md#procedural-meshes) + - [Concave and Non-planar Polygons](meshes.md#concave-and-non-planar-polygons) + - [Paths](paths.md) + - [Points](paths.md#points) + - [Curves](paths.md#curves) + - [Arcs](paths.md#arcs) + - [Circles](paths.md#circles) + - [Rectangles](paths.md#rectangles) + - [Regular Polygons](paths.md#regular-polygons) + - [Procedural Paths](paths.md#procedural-paths) + - [Nested Paths](paths.md#nested-paths) + - [Path Colors](paths.md#path-colors) + - [SVG Paths](paths.md#svg-paths) + - [Text](text.md) + - [Interpolation](text.md#interpolation) + - [Wrap Width](text.md#wrap-width) + - [Size and Line Height](text.md#size-and-line-height) + - [Line Spacing](text.md#line-spacing) + - [Position and Orientation](text.md#position-and-orientation) + - [Font](text.md#font) + - [Builders](builders.md) + - [Fill](builders.md#fill) + - [Lathe](builders.md#lathe) + - [Extrude](builders.md#extrude) + - [Loft](builders.md#loft) + - [Hull](builders.md#hull) + - [Constructive Solid Geometry](csg.md) + - [Difference](csg.md#difference) + - [Intersection](csg.md#intersection) + - [Union](csg.md#union) + - [XOR](csg.md#xor) + - [Stencil](csg.md#stencil) + - [Groups](groups.md) + - [Lights](lights.md) + - [Ambient](lights.md#ambient) + - [Directional](lights.md#directional) + - [Point Lights](lights.md#point-lights) + - [Spotlights](lights.md#spotlights) + - [Debugging](lights.md#debugging) + - [Cameras](cameras.md) + - [Custom Cameras](cameras.md#custom-cameras) + - [Position](cameras.md#position) + - [Orientation](cameras.md#orientation) + - [Field of View](cameras.md#field-of-view) + - [Orthographic View](cameras.md#orthographic-view) + - [Background](cameras.md#background) + - [Pixel Dimensions](cameras.md#pixel-dimensions) +- Syntax + - [Comments](comments.md) + - [Block Comments](comments.md#block-comments) + - [Nested Comments](comments.md#nested-comments) + - [Literals](literals.md) + - [Strings](literals.md#strings) + - [Vectors and Tuples](literals.md#vectors-and-tuples) + - [Structured Data](literals.md#structured-data) + - [Symbols](symbols.md) + - [Expressions](expressions.md) + - [Operators](expressions.md#operators) + - [Equality and Comparison](expressions.md#equality-and-comparison) + - [Linear Algebra](expressions.md#linear-algebra) + - [Boolean Algebra](expressions.md#boolean-algebra) + - [Members](expressions.md#members) + - [Ranges](expressions.md#ranges) + - [Functions](functions.md) + - [Arithmetic](functions.md#arithmetic) + - [Linear Algebra](functions.md#linear-algebra) + - [Trigonometry](functions.md#trigonometry) + - [Strings](functions.md#strings) + - [Functions and Expressions](functions.md#functions-and-expressions) + - [Custom Functions](functions.md#custom-functions) + - [Commands](commands.md) + - [Background](commands.md#background) + - [Detail](commands.md#detail) + - [Smoothing](commands.md#smoothing) + - [Materials](commands.md#materials) + - [Font](commands.md#font) + - [Transforms](commands.md#transforms) + - [Primitives](commands.md#primitives) + - [Paths](commands.md#paths) + - [Text](commands.md#text) + - [Builders](commands.md#builders) + - [Constructive Solid Geometry](commands.md#constructive-solid-geometry) + - [Random Numbers](commands.md#random-numbers) + - [Debugging](commands.md#debugging) + - [Import](commands.md#import) + - [Control Flow](control-flow.md) + - [Loops](control-flow.md#loops) + - [Loop Index](control-flow.md#loop-index) + - [Looping Backwards](control-flow.md#looping-backwards) + - [Looping Over Values](control-flow.md#looping-over-values) + - [If-Else](control-flow.md#if-else) + - [Conditional Defines](control-flow.md#conditional-defines) + - [Blocks](blocks.md) + - [Options](blocks.md#options) + - [Scope](scope.md) + - [Block Scope](scope.md#block-scope) + - [Function Scope](scope.md#function-scope) + - [Conditional Scope](scope.md#conditional-scope) + - [Debugging](debugging.md) + - [Logging](debugging.md#logging) + - [Assertions](debugging.md#assertions) + - [Import](import.md) + - [Scripts](import.md#scripts) + - [Models](import.md#models) + - [Text and Data](import.md#text-and-data) + - [Dynamic Imports](import.md#dynamic-imports) +- [Export](export.md) + - [Export Formats](export.md#export-formats) + - [Games and AR](export.md#games-and-ar) + - [3D Printing](export.md#3d-printing) + - [Image Formats](export.md#image-formats) + - [Image Options](export.md#image-options) +- [Examples](examples.md) + - [Ball](examples.md#ball) + - [Chessboard](examples.md#chessboard) + - [Cog](examples.md#cog) + - [Earth](examples.md#earth) + - [Icosahedron](examples.md#icosahedron) + - [Spirals](examples.md#spirals) + - [Spring](examples.md#spring) + - [Train](examples.md#train) +- [Glossary](glossary.md) diff --git a/docs/1.7.1/ios/lights.md b/docs/1.7.1/ios/lights.md new file mode 100644 index 00000000..e9808e76 --- /dev/null +++ b/docs/1.7.1/ios/lights.md @@ -0,0 +1,177 @@ +Lights +--- + +By default, scenes created in ShapeScript are lit with a bright, white, camera-aligned directional light. This is good for viewing purposes, but limited. + +ShapeScript allows you to add your own lights to the scene, which can help produce a nicer effect when rendering a scene for display. + +Lights come in four basic types, all created using the `light` command. + +**Note:** there is a maximum of 8 non-ambient lights per scene. If you need more, consider pre-lighting your models using [colors](materials.md#color) or [textures](materials.md#texture). + +## Ambient + +An ambient light is one that illuminates all objects in the scene uniformly. It does not have a position or direction, and does not cast shadows. + +To define an ambient light, add the following to your scene: + +```swift +light {} +``` + +This creates a white ambient light at full brightness. The ambient light is applied in addition to the default lighting, with the result that the scene appears over-bright, or *washed out*: + +![Over-bright ambient light](../../images/washed-out.png) + +To solve this, you can reduce the intensity of the light by setting its color: + +```swift +light { + color 0.5 // 50% intensity +} +``` + +The `color` parameter of the light behaves just like any other [color](materials.md#color) in ShapeScript. You can pass up to four values representing the red, green blue and alpha components. + +```swift +light { + color 1 0 1 // purple light +} + +light { + color #FF00FF // purple again, this time specified as a hex color literal +} +``` + +Alpha transparency isn't really meaningful in the context of a light source, so you can think of the fourth (alpha) component of the color as its *intensity*. In the first example above we uses a color value of 0.5 to indicate 50% intensity, but an alternative way to specify this would be to use a color with 50% alpha: + +```swift +light { + color 1 0.5 // white light with 50% intensity +} + +light { + color #FF7F00 0.25 // orange light with 25% intensity +} +``` + +The advantage of this is that you can use the alpha component to vary the intensity of an arbitrary color, rather that having to calculate each component individually. + +## Directional + +Directional lights simulate a distant light source, like the Sun. To create a directional light, add an `orientation` component to your light: + +```swift +light { + color white + orientation 0 0 0 +} +``` + +An orientation of `0 0 0` (or just `0` for short) creates a light that points directly down the Z axis, and is equivalent to the default light for a front-facing camera (unlike the default lighting, this light won't move with the camera if you rotate the view however). + +The three values represent the rotation around the Z, Y and X axes respectively. To create a light that shines from the left you could use: + +```swift +light { + orientation 0 0.5 0 +} +``` + +This rotates the light by 90 degrees (`0.5 * 180`) around the vertical (Y) axis. + +![Directional light shining from the left](../../images/left-light.png) + +**Note:** unlike ambient light, adding a directional light to the scene disables/replaces the default lighting. + +Lights are also affected by [relative transforms](transforms.md#relative-transforms), so the following would be equivalent to the above: + +```swift +rotate 0 0.5 0 +camera { + orientation 0 +} +``` + +**Note:** the `orientation 0` is still required, otherwise the light will be treated as ambient and will not be affected by the rotation. + +## Point Lights + +A point light is an omnidirectional light that exists at a particular location in space. It has a position, but no orientation: + +```swift +camera { + position -5 0 0 +} +``` + +As you can see, the effect of placing a point light to the left of your scene is similar to a direction light facing from this direction. Unlike a directional light however, a point light can be placed in between objects in the scene: + +```swift +camera { + position 0.5 0 0 +} +``` + +![Point light between objects](../../images/point-light.png) + +## Spotlights + +A spotlight has both a position *and* direction. The following code creates a yellow spotlight located just to the left of the enter of the scene, pointing down and to the right: + +```swift +light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 +} +``` + +![Spotlight](../../images/spotlight.png) + +By default, spotlights cast light in a 45-degree cone. You can alter this with the `spread` option: + +```swift +light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 + spread 0.5 +} +``` + +The spread is a value between 0 and 1 representing a cone angle from 0 to 180 degrees, so `0.5` in the example maps to a 90-degree cone: + +![Wide spotlight](../../images/spotlight-wide.png) + +You've probably noticed that the edge of the light cast by the spotlight is blurred. The blurred region around the spotlight is known as the [penumbra](https://en.wikipedia.org/wiki/Umbra,_penumbra_and_antumbra#Penumbra), and can be adjusted using the `penumbra` option: + +```swift +light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 + penumbra 0 +} +``` + +The `penumbra` is specified as a value between 0 and 1 representing the proportion of the total spotlight cone that should be blurred. The default is `1` (meaning the blur is spread right across the cone). A value of zero results in a completely sharp edge: + +![Sharp spotlight](../../images/spotlight-sharp.png) + +## Debugging + +Since it can be hard to visualize the position of a spot or point light, you can use the [debug command](debugging.md) to make the light temporarily visible in the ShapeScript viewer: + +```swift +debug light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 +} +```swift + +![Debugging a spotlight](../../images/spotlight-debug.png) + +--- +[Index](index.md) | Next: [Cameras](cameras.md) diff --git a/docs/1.7.1/ios/literals.md b/docs/1.7.1/ios/literals.md new file mode 100644 index 00000000..6334cd03 --- /dev/null +++ b/docs/1.7.1/ios/literals.md @@ -0,0 +1,200 @@ +Literals +--- + +Literals are values that you type in your ShapeScript file. They could be numbers, text or something else. Literals can be passed as parameters to [options](options.md), [functions](functions.md) or [commands](commands.md), or used in [expressions](expressions.md). Here are some examples: + +```swift +5 // an integer literal + +37.2 // a decimal literal + +false // a boolean literal + +"hello" // a string literal + +1 0 0 // a vector literal + +#FF0000 // a hex color literal +``` + +## Strings + +Strings are how human-readable text is represented in ShapeScript. These are used for a variety of purposes, including specifying file names for [imports](import.md) or [textures](materials.md#texture), [logging](debugging.md#logging) debug information to the console, or even rendering 3D text. + +String literals are delimited by double quotes (`"`) to prevent ambiguity if the text matches a keyword or [symbol](symbols.md), or contains spaces or other punctuation. If you want to use a double quote *inside* the text itself then it must be *escaped* using a backslash (`\`) character: + +```swift +"hello \"Bob\" (if that's your real name)" +``` + +A line containing a string literal without a closing `"` is treated as an error, so if you need to include line-breaks in your string then these must also be escaped. Use the escape sequence `\n` (short for "new line") to indicate a line-break: + +```swift +"first line\nsecond line" +``` + +Because `\` is used as the escape character, it must also be escaped if you want it to appear literally in the string. Use a double `\\` in this case (there is no need to escape forward slashes (`/`) however): + +```swift +"back slash: \\" +"forward slash: /" +``` + +## Vectors and Tuples + +A sequence of values separated by spaces defines a [tuple](https://en.wikipedia.org/wiki/Tuple). A tuple of numeric values is also known as a *vector*. + +Vectors are used in ShapeScript to represent positions, colors, sizes and rotations. Many [commands](https://github.com/nicklockwood/ShapeScript/blob/develop/Help/commands.md) in ShapeScript accept a vector argument, and you can pass a tuple to these commands directly: + +```swift +translate 1 0 0 +``` + +You can also [define](symbols.md) a tuple value to use later: + +```swift +define size 1 1 0.5 +``` + +A tuple defined in this way doesn't know if it's going to be used as a vector - it's just a sequence of numbers. We might guess from the name "size" that it will be used to set the size of something, but there's nothing preventing you from using it as, say, a [color](materials.md#color) value: + +```swift +define size 1 1 0.5 +color size // sets color to yellow +``` + +Tuples are't limited to numbers. They can be comprised of any type of value (including other tuples), or a mix of different types: + +```swift +define size 1 2 3 +define myTuple2 "hello" 5.3 size +``` + +## Structured Data + +As well as simple values like numbers and text, it can sometimes be useful to group together sets of related data. Tuples can be nested arbitrarily to create complex data structures: + +```swift +define matrix (1 2 3) (4 5 6) (7 8 9) +``` + +Parentheses are used here to indicate that this is a tuple of three nested tuples, and not a single tuple of nine numbers. To make this more readable, you can wrap the data over multiple lines inside outer parentheses, and even use [comments](comments.md) if you wish: + +```swift +define matrix ( + (1 2 3) // position + (4 5 6) // scale + (7 8 9) // orientation +) +``` + +**Note:** The line breaks have no semantic meaning here, they only serve to make the code more readable. Only the parentheses are used to determine the grouping. + +Since no built-in commands in ShapeScript consume structured data like this, you need a way to access individual elements. You can do this in two ways: + +To extract individual values from a tuple, you can use [member syntax](expressions.md#members). If the tuple is numeric and shaped like a vector, size, rotation or color then you can use the `x`/`y`/`z`, `width`/`height`/`depth`, `roll`/`yaw`/`pitch` or `red`/`green`/`blue`/`alpha` members respectively: + +```swift +define pos 1 2 +print pos.x // 1 +print pos.y // 2 +print pos.z // 0 + +define size 3 4 5 +print size.width // 3 +print size.height // 4 +print size.depth // 5 + +define rotation 0.5 0.25 0 +print rotation.roll // 0.5 +print rotation.yaw // 0.25 +print rotation.pitch // 0 + +define col 1 0 0 +print col.red // 1 +print col.green // 0 +print col.blue // 0 +print col.alpha // 1 +``` + +For more abstract data, you can use the ordinal members (`first`, `second`, `third`, ... `last`) to access members by index: + +```swift +define data ( + "cube" // name + (1 2 3) // position + #ff0000 // color +) + +print data.count // 3 +print data.first // name +print data.second // position +print data.last // color +``` + +Member expressions can be chained, so something like this will also work: + +```swift +print data.second.x // x component of the position +``` + +You can split up lists of data using the `allButFirst` and `allButLast` members: + +```swift +define data (1 2 3 4 5) + +print data.allButFirst // 2 3 4 5 +print data.allButLast // 1 2 3 4 +``` + +For list-like data, you can use a [for loop](control-flow.md#looping-over-values) to loop over the top-level values: + +```swift +define positions ( + (1 1 2) + (2 1 2) + (3 1 2) +) + +for p in positions { + cube { + position p + } +} +``` + +You can even use nested loops to access sub-elements: + +```swift +define matrix ( + (1 2 3) + (4 5 6) + (7 8 9) +) + +for row in matrix { + for column in row { + print column // prints 1 to 9 + } +} +``` + +**Note:** There is no requirement that rows in such a structure are the same type or length: + +```swift +define data ( + (1 2 3) + (10) // single element + () // empty tuple + ("hello" "world") // non-numeric +) + +for row in data { + for element in row { + print element // prints 1, 2, 3, 10, "hello", "world" + } +} +``` + +--- +[Index](index.md) | Next: [Symbols](symbols.md) diff --git a/docs/1.7.1/ios/materials.md b/docs/1.7.1/ios/materials.md new file mode 100644 index 00000000..135f9633 --- /dev/null +++ b/docs/1.7.1/ios/materials.md @@ -0,0 +1,239 @@ +Materials +--- + +By default, all geometry that you create in ShapeScript appears as if it were made of a matte white plastic. You can alter this appearance using *materials*. + +Material support in ShapeScript is fairly limited at the moment, but you can set the *color* and *texture* of shapes. + +A given shape can only have either a color or texture, but not both. Setting the texture will clear the color and vice-versa. + +## Color + +You can alter the color of your shapes using the `color` (or `colour`) command, which accepts a color value in a variety of formats: Numeric RGB, hexadecimal or predefined: + +The following commands all produce a red cube: + +```swift +cube { color 1 0 0 } +cube { color #FF0000 } +cube { color #F00 } +cube { color red } +``` + +Because `color` is a command rather than an option, you can use it anywhere in your program and it will affect all subsequent shapes that you create. If you use the `color` command *inside* a shape or [group](groups.md) then its effect will end at the closing `}`: + +```swift +color green +group { + color red + cube // red cube +} +sphere // green sphere +``` + +### RGB Colors + +As mentioned in [Getting Started](getting-started.md), `color` can accept a tuple of up to 4 values that represent the red, green, blue and alpha color channels respectively. Values should be specified in the range 0 (no color) to 1 (full color): + +```swift +color 1 0 0 // bright red +color 0.5 0 0 // a darker shade of red +color 0.5 0.5 0 // an olive green +``` + +The red, green and blue channels control the color itself, and the alpha channel controls transparency. An alpha of 0 is fully transparent (invisible), and an alpha of 1 is fully opaque. If omitted, the alpha value defaults to 1. Alpha values between 0 and 1 can be used to create *translucent* colors, which blend with the background behind them. The following code would produce a 50% transparent red sphere: + +```swift +sphere { color 1 0 0 0.5 } +``` + +If fewer than 3 parameters are passed to the `color` command, the first parameter is treated as the overall luminance (brightness) value, meaning that the resultant color will be set to a shade of gray between 0 (black) and 1 (white). + +The table below shows how RGBA color values are interpreted, based on the number of components: + +Number of parameters | Meaning +:--------------------------- | :-------------------------- +1 | Luminance +2 | Luminance and alpha +3 | Red, green and blue +4 | Red, green, blue and alpha + +
+ +For example, the following both produce a light shade gray of gray, with a luminance of 0.8: + +```swift +color 0.8 +color 0.8 0.8 0.8 +``` + +And the following both produce 50% a translucent white, with full luminance and 50% alpha : + +```swift +color 1 0.5 +color 1 1 1 0.5 +``` + +### Hexadecimal Colors + +Instead of numeric values, you can use web-style hex codes to specify colors. These consist of a hash character (`#`) followed by 3 or 4 pairs of hexadecimal digits to specify color components in the range 0-255. Hex color codes are a popular convention and are supported by many graphics tools. Here are some examples: + +```swift +color #FF0000 // pure red +color #7F7F7F // 50% gray +color #000000 // pure black +``` + +As with numeric RGB colors, hex colors are opaque by default, but you can vary the alpha by adding a fourth pair of hexadecimal digits in the range `00` (fully transparent) to `FF` (fully opaque)`. A value of `7F` results in 50% transparency: + +```swift +color #FF00007F // 50% transparent red +``` + +As with [web colors](https://en.wikipedia.org/wiki/Web_colors), you can use a three-digit "short hex" format as follows: + +```swift +color #F00 // equivalent to #FF0000 +``` + +And you can also use a fourth digit to specify alpha: + +```swift +color #F006 // equivalent to #FF000066 +``` + +### Predefined Colors + +ShapeScript defines some built-in color constants for you to use: + +Name | R G B | Hexadecimal | Short Hex +:---------| :-----------| :-----------| :--------- +black | 0 0 0 | #000000 | #000 +blue | 0 0 1 | #0000FF | #00F +green | 0 1 0 | #00FF00 | #0F0 +cyan | 0 1 1 | #00FFFF | #0FF +red | 1 0 0 | #FF0000 | #F00 +magenta | 1 0 1 | #FF00FF | #F0F +yellow | 1 1 0 | #FFFF00 | #FF0 +white | 1 1 1 | #FFFFFF | #FFF +orange | 1 0.5 0 | #FF7F00 | - +gray/grey | 0.5 0.5 0.5 | #7F7F7F | - + +
+ +You can override these built-in colors using the `define` command, or define new colors of your own: + +```swift +define red 1 0.3 0.1 // override the default red color with a custom shade + +define lightGray 0.8 // define a new color "lightGray" with 80% luminance +``` + +### Alpha Override + +The predefined colors are all opaque (have an alpha of 1) by default, but you can override the alpha as follows: + +```swift +color red 0.5 // set color to red with 50% alpha + +define greenGlass green 0.2 +color greenGlass // set color to green with 20% alpha +``` + +This approach also works for user-defined color constants, and with RGB and hexadecimal color literals: + +```swift +define skyBlue 0.5 0.8 1 // opaque blue +color skyBlue 0.5 // 50% transparent blue + +color #ff0 0.5 // 50% transparent yellow +``` + +## Texture + +A texture is an image that is wrapped around a 3D shape, either as decoration, or to give the appearance of more surface detail than is actually there. + +You can set the texture for your shapes using the `texture` command: + +```swift +sphere { + texture "filename.png" +} +``` + +The parameter for the `texture` command is the name of an external image file to display. The name can include either an absolute or relative file path, enclosed in double quotes. If a relative path is used it should be specified relative to the ShapeScript file that references it. + +The texture name can be constructed dynamically by using [string interpolation](text.md#interpolation), which is useful if you have multiple texture files with a common prefix or suffix: + +```swift +for n in 1 to 5 { + cube { + texture "file" n ".png" + position n + } +} +``` + +As with `color`, once a texture has been set it will be applied to all subsequent shapes defined in the same [scope](scope.md). To clear the current texture you can set it to an empty string: + +```swift +texture "" +``` + +### Access Permission + +The first time you try to use an image, you will see an error screen like the one below. + +![Sandbox error](../../images/sandbox-error-ios-1.6.4.png) + +This is because Apple employs a security feature called [sandboxing](https://en.wikipedia.org/wiki/Sandbox_(computer_security)) to prevent apps from accessing files without the user's permission. Use the `Grant Access` button to open the containing folder for your images. If you prefer, you can just grant access to the specific image file, but in that case you will need to grant access individually to each new texture that you use. + +### Texture Wrapping + +How a texture is applied depends on the shape. Different shape types have different default wrapping schemes: + +![Textured cube](../../images/textured-cube.png) + +![Textured sphere](../../images/textured-sphere.png) + +![Textured cylinder](../../images/textured-cylinder.png) + +![Textured cone](../../images/textured-cone.png) + +Currently there is no way to override the default wrapping scheme, but the way that you create a shape affects the way that it is textured. + +For example, creating a cube using the `cube` command will result in a different texture wrapping effect than creating it by [extruding](builders.md#extrude) a square. + +## Opacity + +Opacity is a measure of how transparent an object is. You can vary the opacity for an object or group using the alpha property of the `color` (as described [above](#color)), but sometimes you may want to vary the opacity of a whole tree of differently-colored objects, and for that you can use the `opacity` command: + +```swift +opacity 0.5 +group { + color 1 0 0 // opaque red + cube // 50% transparent red cube + color 0 1 0 // opaque green + sphere // 50% transparent green sphere +} +``` + +Setting `opacity` affects all subsequently defined objects up until the end of the current [scope](scope.md). The `color` or `texture` of each object is multiplied by the current `opacity` value, so a `color` with alpha 0.5 combined with an `opacity` of 0.5 will result in the object being drawn with an overall opacity of 0.25. + +Opacity is applied hierarchically. The value you specify with the `opacity` command is multiplied by the value set in the parent scope, so drawing an object with `opacity` 1.0 inside a scope with `opacity` 0.5 will result in in an opacity of 0.5, not 1.0, because the inner value is relative to the outer value. + +As with the alpha property of `color`, `opacity` is measured in the range 0 (fully transparent) to 1 (fully opaque), however you can specify `opacity` values higher than 1. This can be useful for making an object *more* opaque than the base level for its current scope. For example, by setting `opacity` to 1 / [the parent scope opacity], you can cancel it out: + +```swift +opacity 0.5 +group { + color 1 0 0 // opaque red + cube // 50% transparent red cube + color 0 1 0 // opaque green + opacity 2 // cancel out the 50% opacity + sphere // opaque green sphere +} +``` + +--- +[Index](index.md) | Next: [Transforms](transforms.md) diff --git a/docs/1.7.1/ios/meshes.md b/docs/1.7.1/ios/meshes.md new file mode 100644 index 00000000..c78cc732 --- /dev/null +++ b/docs/1.7.1/ios/meshes.md @@ -0,0 +1,128 @@ +Meshes +--- + +While they may appear solid, the 3D shapes that ShapeScript produces are actually hollow shells composed of polygons. These polygon shells are known as [meshes](https://en.wikipedia.org/wiki/Polygon_mesh). + +ShapeScript provides many tools for constructing meshes, including [primitives](primitives.md), [builders](builders.md) and [CSG](csg.md) operations, but you can also create meshes manually by specifying each polygon yourself. While this is generally a tedious approach compared to the other tools, it affords you complete control over the mesh topology, and allows you to create arbitrary shapes that might be hard to achieve with higher-level tools. + +## Polygons and Points + +A mesh is made up of one or more *polygons*, and each polygon is made up of three or more [vertices](https://en.wikipedia.org/wiki/Vertex_(geometry)) or *points*. To construct a mesh you use the `mesh`, `polygon` and `point` commands. The following code defines the simplest possible mesh - a single triangle: + +```swift +mesh { + polygon { + point 0 0 + point 1 0 + point 1 1 + } +} +``` + +![Triangle](../../images/triangle-polygon.png) + +The `point` command accepts a [vector](literals.md#vectors-and-tuples) value. Polygons can be placed anywhere in three-dimensional space, so `point` accepts up to three values (for the X, Y and Z coordinates respectively). In this case we've just specified two values, so the Z coordinate defaults to zero. + +Manually constructed meshes do not currently support textures, but they inherit the current [material color](materials.md#color). Color can also be applied separately to individual vertices within a mesh. When vertices of a given polygon have different colors applied, the color will be smoothly interpolated between them, creating a gradient: + +```swift +mesh { + polygon { + color red + point 0 0 + color green + point 1 0 + color blue + point 1 1 + } +} +``` + +![Colored triangle](../../images/colored-triangle.png) + +## Watertightness + +If you rotate the triangle you will see that the back face is invisible. Most of the mesh-construction commands in ShapeScript produce shapes that are *watertight*, meaning that they do not contain any holes that would allow you to see the back faces of the polygon surface, but when you create a mesh using the `mesh` command it's up to you to ensure that the mesh you create is watertight. You can check this by selecting the mesh in the editor and [getting info](getting-started.md#debugging-and-selection). As you can see, our triangle is not watertight: + +![Not watertight](../../images/not-watertight.png) + +Watertightness is an important quality when using [CSG](csg.md) operations, because the "solid" in Constructive Solid Geometry implies that shapes are expected to behave like solid objects (even though they are actually hollow), and holes or exposed back faces will cause glitches as ShapeScript is unable to determine whether a given point lies inside or outside the shape. + +To solve this, we can add another triangle with the same vertices but facing the opposite direction. But wait - we never specified the direction of the triangle face in the first place, so how does ShapeScript decide which way a polygon is facing? + +## Winding order + +By convention, polygons in ShapeScript are assumed to be defined with [counterclockwise](https://en.wikipedia.org/wiki/Counterclockwise) (aka *anticlockwise*) winding. What that means is that when looking at the polygon from the front, the vertices will be ordered in counterclockwise direction. To add a back face we'll create a second triangle with the inverse vertex order: + +```swift +mesh { + // Front + polygon { + color red + point 0 0 + color green + point 1 0 + color blue + point 1 1 + } + // Back + polygon { + point 1 1 + point 1 0 + point 0 0 + } +} +``` + +(We've left the back face white in this case, so you can tell which side is which). If you get info again you'll see that the triangle is now watertight: + +## Procedural Meshes + +We've learned how to make a triangle, but what about something a bit more complex? For our next trick we're going to create a cube. + +A cube has six faces, each consisting of four vertices - 24 vertices in total. But in fact there are really only eight unique vertices, so rather than tediously typing out the same points over and over, maybe we can use the power of scripting to save ourselves some effort? + +First we'll define a [block](blocks.md) called "side" that creates a single side of the cube: + +```swift +define side { + color rnd rnd rnd + polygon { + point -0.5 -0.5 0.5 + point +0.5 -0.5 0.5 + point +0.5 +0.5 0.5 + point -0.5 +0.5 0.5 + } +} +``` + +Note that the `color rnd rnd rnd` - this will produce a different random color each time the block is called, making the cube a little more interesting to look at. + +Next we'll call `side` block six times from inside a `mesh`, passing a different orientation each time: + +```swift +mesh { + side { orientation 0 } + side { orientation 0 0.5 } + side { orientation 0 1 } + side { orientation 0 1.5 } + side { orientation 0 0 0.5 } + side { orientation 0 0 -0.5 } +} +``` + +And voila! A colored cube: + +![Cube mesh](../../images/cube-mesh.png) + +Obviously this is a lot more trouble than just using the [cube](primitives.md#cube) command, but hopefully you can see the potential for creating more interesting shapes. + +## Concave and Non-planar Polygons + +In many 3D engines, meshes must be constructed exclusively from triangles. The reason for this is that by definition a triangle is always both [convex](https://en.wikipedia.org/wiki/Convex_polygon) and *planar*, meaning that all its vertices lie on the same plane, which simplifies the mathematics needed to [rasterize](https://en.wikipedia.org/wiki/Rasterisation) or render the mesh. + +ShapeScript places no such restrictions on the polygons that you define in your mesh. Non-planar or non-convex polygons will be automatically split into their constituent triangles as needed for [export](export.md) or display. + +--- +[Index](index.md) | Next: [Paths](paths.md) + diff --git a/docs/1.7.1/ios/options.md b/docs/1.7.1/ios/options.md new file mode 100644 index 00000000..ce87ccd5 --- /dev/null +++ b/docs/1.7.1/ios/options.md @@ -0,0 +1,102 @@ +Options +--- + +All 3D shapes in ShapeScript have a common set of options that you can configure. As implied by the name, options are *optional* - they always have sensible default values that will be used if you don't specify a value. + +Some shapes have extra options, specific to that shape. Later you will learn how to define custom options on your own shapes using the [option command](blocks.md#options). + +An option is denoted by a name followed by one or more values or expressions inside a shape [block](blocks.md). Different options accept different value types, but typically these will be a number, vector or text. Here are some examples: + +```swift +cube { + detail 5 // a numeric value + position 1 0 -1 // a vector value + texture "Earth.png" // a texture value +} +``` + +## Name + +The `name` option allows you to assign a name to a given shape or group, for example: + +```swift +cylinder { + name "Wheel" + size 1 1 0.1 +} +``` + +The name can contain spaces or punctuation, and is wrapped in double quotes to prevent ambiguity with other symbols (see [literals](literals.md) for details). + +The name can be useful for identifying distinct shape components when [selecting them](getting-started.md#debugging-and-selection) in the viewer, or when importing a model exported from ShapeScript into another application (see the [export](export.md) section for details). + +## Detail + +As discussed in the [getting started](getting-started.md) and [primitives](primitives.md) sections, curved shapes cannot be represented exactly using triangles, so they must be approximated to a specified level of detail. + +ShapeScript allows you to configure that detail setting using the `detail` command: + +```swift +sphere { detail 32 } +``` + +Unlike `name`, `detail` is not actually an option, but a global command. You can change detail level at any point within your ShapeScript file, and it will affect all shapes defined subsequently up to the end of the current [scope](scope.md). + +The detail level can be overridden hierarchically, so a `detail` command inside a shape will take precedence over a `detail` command in its containing scope: + +```swift +detail 8 + +sphere { detail 32 } // has detail of 32 + +cylinder { position 1 } // has detail of 8 +``` + +![Detail](../../images/detail.png) + +The `detail` command accepts a single integer value, which represents the number of straight sections used to approximate a circle. This is directly applicable to shapes that have circular sections, such as a sphere or cylinder, as well as to circular [paths](paths.md). + +For curved shapes that are not circular, such as a custom [path](paths.md), the relationship between the `detail` value and the number of sections is not quite so straightforward, but typically 1/4 of the `detail` value will be applied to each curved section of the path. + +## Smoothing + +Similar to `detail`, `smoothing` is used to control the appearance of curved shapes. + +As mentioned above, all shapes in ShapeScript are formed from flat triangles. To create the illusion of a curved surface, lighting can be smoothly interpolated across polygon faces to give the appearance of curvature. Lighting is calculated using [surface normals](https://en.wikipedia.org/wiki/Normal_(geometry)) - vectors pointing outwards from each [vertex](https://en.wikipedia.org/wiki/Vertex_(geometry)) in a mesh that are used to indicate when the simulated curvature differs from the geometric reality. + +[Primitive shapes](primitives.md) in ShapeScript all have appropriate normals set by default, and when creating [paths](paths.md) you can use the `curve` command to specify when a corner should appear curved rather than sharp. However, there are times when you may wish to override the default behavior, e.g. to deliberately create a more angular appearance, or to smooth an [imported](import.md) model that does not already include appropriate surface normal data. + +The `smoothing` command accepts a numeric value in the range 0 to 1. This represents an angle between 0 and 180 degrees (see the [trigonometry section](functions.md#trigonometry) for more about how angles are represented in ShapeScript). This angle is the threshold at which ShapeScript will apply normal-based smoothing. Edges that meet at a greater angle than this threshold will be rendered as a sharp seam, and those that meet at lesser angle will appear as a smooth curve. + +On that basis, a `smoothing` value of 0 means all edges will appear sharp. A value of 1 means all edges will appear rounded (this may look a little strange). A value of 0.5 (90 degrees) means that [obtuse](https://en.wikipedia.org/wiki/Angle#Types_of_angles) edges will appear rounded and acute ones will appear sharp. + +Like `detail`, `smoothing` is a global option that applies hierarchically. You set it once at the top of the file, individually inside each shape, or any combination: + +```swift +smoothing 0 // flat shading + +cylinder { smoothing 0.5 } // smooth shading + +sphere { position 1 } // inherits flat-shading from file scope +``` + +![Smoothing](../../images/smoothing.png) + +## Transform + +Every shape has a position, orientation and size in space. Collectively, these are known as the shape's *transform*, and they can be set using the following options: + +- `position x y z` +- `orientation roll yaw pitch` +- `size width height depth` + +For more information on how these are used, see the [transforms](transforms.md) section. + +## Material + +You can set the color and texture of a shape (collectively known as its *material*) by using the `color` and `texture` options. Like `detail`, `color` and `texture` are not actually options, but global commands that can be set anywhere in your ShapeScript file and will apply to all subsequent shapes within the current [scope](scope.md). + +For more information about materials, see the [materials](materials.md) section. + +--- +[Index](index.md) | Next: [Materials](materials.md) diff --git a/docs/1.7.1/ios/paths.md b/docs/1.7.1/ios/paths.md new file mode 100644 index 00000000..773e83fd --- /dev/null +++ b/docs/1.7.1/ios/paths.md @@ -0,0 +1,313 @@ +Paths +--- + +A path is a sequence of line segments or curves joined end-to-end. The points of the path can be positioned anywhere in 3D space, and the path can be open-ended or closed. + +Paths are not typically used directly as part of a scene (except possibly for something like a thin rope or antenna), but they can be used to define the contours or profile of a 3D shape (see [builders](builders.md) for details). + +## Points + +As with the [polygons](meshes.md#polygons-and-points) in a mesh, you define a path using a series of points. A path must have at least two points to be visible. The following path defines a short, horizontal line along the X axis. + +```swift +path { + point -1 0 + point 1 0 +} +``` + +![Line](../../images/line.png) + +In the above example we used the `point` command, which accepts a [vector](literals.md#vectors-and-tuples) value. Paths can be three-dimensional, so `point` accepts up to three coordinates, but most paths that you create in practice will be 2D (all points will have a Z value of zero). + +Paths can be open or closed (meaning that the points form an unbroken loop). To create a closed path, the first and last point must have the same position. The following path has four points, but it actually describes a triangle rather than a quadrilateral, because the first and last points are the same: + +```swift +path { + point 0 1 + point -1 -1 + point 1 -1 + point 0 1 +} +``` + +![Triangle](../../images/triangle.png) + +## Curves + +Points are great for defining polygonal shapes, but what about curves? + +Curves in ShapeScript are typically created using [quadratic Béziers](https://en.wikipedia.org/wiki/Bézier_curve). A quadratic Bézier is defined by two end-points and a control point. + +The curve passes through the end-points, but does not pass through the control point. Instead, the control point defines the point of intersection for the tangents of the curve as it passes through the end-points. This sounds complicated, but it's quite straightforward to use in practice. + +To create a smooth curve, use the `point` command to define end points, and the `curve` command to define control points. For example, the following creates a smooth arc: + +```swift +path { + point -1 -1 + curve 0 1 + point 1 -1 +} +``` + +![Arc](../../images/arc.png) + +You may notice that this "smooth" arc is not actually very smooth. Just as [3D meshes](meshes.md) are constructed from flat polygons, curves are approximated using straight lines. You can adjust the smoothness of curves in a path using the `detail` command: + +```swift +path { + detail 99 + point -1 -1 + curve 0 1 + point 1 -1 +} +``` + +![Smooth arc](../../images/smooth-arc.png) + +If multiple `curve` commands (control points) are used in sequence, an end-point will be interpolated at the mid-point between them. This allows you to easily create complex curves such as an "S" shape. + +You can also create closed paths entirely using `curve` points. Eight `curve` points arranged in an octagon can closely approximate a circle (the ninth point is just a duplicate of the first, to close the path): + +```swift +path { + curve -0.414 1 + curve 0.414 1 + curve 1 0.414 + curve 1 -0.414 + curve 0.414 -1 + curve -0.414 -1 + curve -1 -0.414 + curve -1 0.414 + curve -0.414 1 +} +``` + +![Circle](../../images/octocircle.png) + +## Arcs + +Arcs created by Bézier curves can approximate a circle, but not perfectly. If you need a precisely circular curve, you can use the `arc` command: + +```swift +arc { + angle 0.5 +} +``` + +The `angle` option defines the angular span of the arc, starting clockwise from the Y axis. The angle is measured in *half-turns* (as explained in the [Transforms](transforms.md#orientation) section) so the `0.5` in the example above produces a quarter circle: + +![Quarter Circle](../../images/quarter-circle.png) + +To adjust the starting angle, you can use the [orientation option](transforms.md#orientation) or [rotate command](transforms.md#relative-transforms) as you would for any other shape. The following creates an arc from -45 degrees to +90 degrees: + +```swift +arc { + orientation -0.25 + angle 0.75 +} +``` + +Arcs have a default radius of 0.5 units. You can increase or decrease this using the `size` option: + +```swift +arc { + size 2 // radius of 1 unit (0.5 * 2) +} +``` + +Arcs can be used in conjunction with individual path points or other arcs to make more complex shapes. The following creates a slab with curved corners: + +```swift +extrude path { + // left arc + arc { + angle -0.5 + } + // bottom-left corner + point -0.5 0 + // bottom-right corner + point 1.5 0 + // right arc + arc { + position 1 0 + orientation 0.5 + angle -0.5 + } + // close path with smooth join + curve 0 0.5 +} +``` + +![Curved slab](../../images/curved-slab.png) + +## Circles + +If you need a complete circle, there's an even simpler way than using `arc`, which is to use the `circle` command: + +```swift +circle +``` + +Like the `sphere` primitive, the `circle` path has a default diameter of 1 unit, and its smoothness is controlled by the current `detail` setting, but these can both be overridden by passing explicit `size` and `detail` options: + +```swift +circle { + size 2 + detail 64 +} +``` + +## Rectangles + +To create a square or rectangle you can use the `square` command: + +```swift +square +``` + +Like `circle`, `square` accepts a `size` option. The following will create a rectangle exactly twice as wide as it is tall: + +```swift +square { + size 2 1 +} +``` + +![Rectangle](../../images/rectangle.png) + +You can even create a rectangle with rounded corners using the `roundrect` command: + +```swift +roundrect +``` + +In addition to `size`, `roundrect` also accepts a `radius` option, which controls the corner radius: + +```swift +roundrect { + size 1 + radius 0.25 +} +``` + +![Roundrect](../../images/roundrect.png) + +## Regular Polygons + +When used outside of a `mesh`, the `polygon` command can be used to create a regular polygon. The following creates a pentagon: + +```swift +polygon { + sides 5 +} +``` + +![Pentagon](../../images/pentagon.png) + +The output for this is similar to using the `circle` command with a specific detail value: + +```swift +circle { + detail 5 +} +``` + +The difference is only apparent if you extrude the result, because in the circle's case the extruded sides are [smoothed](options.md#smoothing) by default, whereas for the polygon they are sharply defined: + +```swift +extrude polygon { + position -1 + sides 6 +} + +extrude circle { + position 1 + detail 6 +} +``` + +![Polygon vs circle](../../images/polygon-vs-circle.png) + +## Procedural Paths + +The `circle` command is fine for creating complete circles, but what if you want a circular arc or semicircle? Calculating the Bézier control points for that manually would be tedious, so this is where the power of ShapeScript's procedural logic comes in handy. + +To create a path procedurally, you can use a [for loop](control-flow.md#loops) along with [relative transform commands](transforms.md#relative-transforms) such as `rotate`. For example, the following code generates a semicircle: + +```swift +path { + for 0 to 8 { + curve 0 1 + rotate 1 / 8 + } +} +``` + +![Semicircle](../../images/semicircle.png) + +## Nested Paths + +A path can be formed from multiple distinct sub-paths. To add additional sub-paths, you can nest a `path` command inside another one (or any other command that returns a path). The following code creates a path made up of two overlapping circles: + +```swift +path { + circle + translate 0.5 + scale 0.5 + circle +} +``` + +![Overlapping Circles](../../images/overlapping-circles.png) + +## Path Colors + +Paths cannot currently be textured, but they inherit the current [material color](materials.md#color). Color can also be applied to individual points within a path. When neighboring path points have different colors applied, the color will be smoothly interpolated between them, creating a gradient: + +```swift +path { + color red + point 0 1 + color green + point -1 -1 + color blue + point 1 -1 +} +``` + +![Path colors](../../images/path-colors.png) + +## SVG Paths + +[Scalable Vector Graphics (SVG)](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) is a popular standard for defining vector graphics. SVG is a fairly complex XML-based format, but a subset of the SVG standard defines a simple text-based [path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData) that uses a series of single-character instructions, each followed by one or more coordinates to define a path. For example, the following SVG path code defines a triangle: + +```svg + +``` + +You can use this syntax in ShapeScript by taking just the `d` attribute from the path and passing it to the `svgpath` command, as follows: + +```swift +fill svgpath "M 100 100 L 300 100 L 200 300 z" +``` + +You can fill or extrude an `svgpath` and adjust its size, position, etc. as follows: + +```swift +fill svgpath { + size 0.01 + position -2 2 + "M 100 100 L 300 100 L 200 300 z" +} +``` + +Which produces the following output: + +![SVG Triangle](../../images/svgpath.png) + +**Note:** SVG uses a flipped vertical coordinate system relative to that used by ShapeScript. To compensate for this, vertical (Y) coordinates passed to the `svgpath` command are treated as negative. + +--- +[Index](index.md) | Next: [Text](text.md) diff --git a/docs/1.7.1/ios/primitives.md b/docs/1.7.1/ios/primitives.md new file mode 100644 index 00000000..302db1e8 --- /dev/null +++ b/docs/1.7.1/ios/primitives.md @@ -0,0 +1,107 @@ +Primitives +--- + +ShapeScript has a number of built-in shapes that you can use to quickly construct a scene. These are known as *primitives*, to distinguish them from more complex custom shapes that you can define using [builders](builders.md) or [CSG](csg.md) operations. + +## Cube + +The `cube` primitive creates a box. You can create a cube just by using the `cube` command, which defines a cube with the default size of one unit: + +```swift +cube +``` + +To alter the size of the cube, you can pass a [block](blocks.md) containing a `size` option followed by up to 3 values specifying the individual dimensions of the cube, or a single value to create a cube with equal sides. + +```swift +cube { size 1 2 } +``` + +![Box](../../images/box.png) + +You can also rotate and position the cube using the `orientation` and `position` options, as follows: + +```swift +cube { + size 1 1 2 + position 1 0 0 + orientation 0.25 +} +``` + +The `size`, `position` and `orientation` options are common to all shapes. For more information about these (and other) options, see the [options](options.md) section. + +## Sphere + +The `sphere` primitive creates a spherical ball. Again, `size` can be used to control the diameter. The following creates a sphere with a diameter of 1 unit (which is the default). + +```swift +sphere { size 1 } +``` + +![Sphere](../../images/sphere.png) + +You may notice that the sphere doesn't look very smooth. As mentioned in the [getting started section](getting-started.md), 3D shapes in ShapeScript are made up of polygons, so curves cannot be represented exactly, only approximated. + +You can improve the quality of the sphere by using the `detail` option: + +```swift +sphere { + detail 32 + size 1 +} +``` + +![Smoother sphere](../../images/smoother-sphere.png) + +You can also pass multiple parameters to `size` to create a *spheroid*: + +```swift +sphere { + detail 32 + size 1 2 3 +} +``` + +![Spheroid](../../images/spheroid.png) + +## Cylinder + +The `cylinder` primitive creates a flat-ended cylinder. + +```swift +cylinder { size 1 } +``` + +![Cylinder](../../images/cylinder.png) + +Like the `sphere` primitive, `cylinder` uses the `detail` command to control its smoothness. If you require a cylinder that is longer or shorter, you can pass a second parameter to the `size` to control the diameter and length independently: + +```swift +cylinder { + detail 64 + size 1 2 +} +``` + +## Cone + +The `cone` primitive creates a conical shape: + +```swift +cone { size 1 } +``` + +![Cone](../../images/cone.png) + +Like `sphere` and `cylinder`, its smoothness is controlled by the `detail` command, and the diameter and length can be controlled independently by passing additional parameters to `size`: + +```swift +cone { + detail 48 + size 1 2 +} +``` + +--- +[Index](index.md) | Next: [Options](options.md) diff --git a/docs/1.7.1/ios/scope.md b/docs/1.7.1/ios/scope.md new file mode 100644 index 00000000..105d6422 --- /dev/null +++ b/docs/1.7.1/ios/scope.md @@ -0,0 +1,25 @@ +Scope +--- + +## Block Scope + +ShapeScript programs are hierarchical, with `{ ... }` braces used to denote the start and end of a block of related options or commands. + +When you define symbols, the effect is limited to the *scope* of the current block, meaning that once the block exits (i.e. when the closing `}` is reached), and symbols defined inside it are discarded, and symbols whose values were overridden inside the block will revert to the value they had before the block was entered. + +Block scope is not just limited to symbols; it also affects [materials](materials.md) and [transforms](transforms.md). When you use the `translate`, `rotate` and `scale` commands to modify the world-transform, or use `color` or `texture` to modify the current material, the effect of those changes is limited to the current block. + +This is convenient, because it means that code that defines subcomponents in your scene can use transforms and materials internally without them "leaking out" and affecting other objects. + +## Function Scope + +When you define a [custom function](functions.md#custom-functions), it also creates a local scope around its body. Like blocks, functions inherit symbols from their parent scope and locally defined symbols will not *leak* out into their parent scope. Unlike blocks, functions can affect the transforms and materials of the scope where they are invoked. + +## Conditional Scope + +Control flow statements like [for loops](control-flow.md#loops) and [if statements](control-flow.md#if-else) also creates a local scope around their body. Like function scope, loop and if/else scope does not apply to transforms or materials, but only to symbols created using the `define` command. + +Any symbols defined inside a `for` loop will be restricted to the inside of the loop body. This also applies to the optional loop index variable. + +--- +[Index](index.md) | Next: [Debugging](debugging.md) diff --git a/docs/1.7.1/ios/symbols.md b/docs/1.7.1/ios/symbols.md new file mode 100644 index 00000000..f563e77a --- /dev/null +++ b/docs/1.7.1/ios/symbols.md @@ -0,0 +1,51 @@ +Symbols +--- + +A symbol is a named value (sometimes referred to as a *constant*) that can be used in place of a [literal](literals.md) value. + +ShapeScript includes several built-in symbols such as `detail` (the current level of detail), or `pi` (the mathematical constant used to compute angles), but you can also define your own symbols using the `define` command: + +```swift +define sides 5 +define red 1 0 0 +``` + +Symbol names consist of a letter followed by zero or more letters, numbers or underscore (`_`) characters. Symbols cannot begin with a number or underscore, and spaces or other punctuation are not allowed. Symbols are case-sensitive, and by convention should begin with a lowercase letter. For multi-word symbols, the recommended convention is to capitalize the first letter of each new word (known as *camelCase* because the capital letters form "humps" in the back of the word): + +```swift +define numberOfSides 7 +``` + +The value assigned to a symbol can be a literal or an [expression](expressions.md). Once defined, a symbol can be used anywhere in place of a literal value, such as inside an expression, or as a [command](commands.md) or [function](functions.md) parameter. Symbols can also be used in the definition of other symbols: + +```swift +define three 3 +define two 2 +define five three + two +``` + +Symbols help to make your ShapeScript file more readable by assigning meaningful names to otherwise inscrutable literal values. They also make the script easier to modify and maintain by avoiding duplication of literal values throughout the code. + +Existing symbols can be redefined by calling `define` again with the same name. If a symbol is defined inside a `{ ... }` block then it will be [scoped](scope.md) to the code inside that block (meaning that it cannot be used after the closing `}`): + +```swift +for i in 1 to 5 { + define foo i // define symbol foo with the current loop index value +} +// foo is undefined here +``` + +Symbols can be *shadowed*, meaning that a symbol defined in an outer scope can be redefined inside an inner scope, but the symbol will revert to its original definition when the scope ends. It is currently only possible to create *constants*, you cannot create *variables* (symbols whose value can be changed later): + +```swift +define foo 1 +for i in 1 to 5 { + define foo i // redefine foo with the current loop index value +} +// foo reverts to original value of 1 after loop terminates +``` + +**Note:** Because of this scoping, it's not possible to conditionally define a value inside an `if` statement and use it outside the `if`. For the correct way to do this, see [conditional defines](control-flow.md#conditional-defines). + +--- +[Index](index.md) | Next: [Expressions](expressions.md) diff --git a/docs/1.7.1/ios/text.md b/docs/1.7.1/ios/text.md new file mode 100644 index 00000000..891a970b --- /dev/null +++ b/docs/1.7.1/ios/text.md @@ -0,0 +1,223 @@ +Text +--- + +The `text` command can be used to generate individual words, lines, or whole paragraphs of text. You use the `text` command as follows: + +```swift +text "Hello, World!" +``` + +To create multiline text you can use the `\n` line-break sequence: + +```swift +text "The quick brown fox\njumps over the lazy dog" +``` + +Or place each line of text on its own line within the file, surrounded by quotes: + +```swift +text { + "The quick brown fox" + "jumps over the lazy dog" +} +``` + +The output of the `text` command is a series of [paths](paths.md), one for each character or *glyph* in the text: + +![Text](../../images/text.png) + +You can use the `fill` or `extrude` commands to turn these paths into a solid mesh (see [builders](builders.md) for details): + +![Solid Text](../../images/solid-text.png) + +## Interpolation + +For your convenience, the text command will automatically convert numeric values to their character representation. The following displays the numbers 1 to 5 as 3D text: + +```swift +for i in 1 to 5 { + extrude text i + translate 1 +} +``` + +![Numbers](../../images/numbers.png) + +You may find that in some cases you need to compose some text dynamically from several source values. ShapeScript has a feature called [string interpolation](https://en.wikipedia.org/wiki/String_interpolation), whereby values can be inserted into a larger body of text. + +To use string interpolation, simply pass multiple values to the `text` command and they will be concatenated together: + +```swift +define apples 5 +define pears 3 +text "Bob has " apples " apples, and " pears " pears" +``` + +Text values are concatenated without spaces, so if you require spaces you should include them explicitly inside quotes: + +```swift +text "Good" "bye" // becomes "Goodbye" +text "Hello " "World" "!" // becomes "Hello World!" +``` + +For your convenience, non-text values will be spaced-out automatically. If you wish to remove the spaces, you can place an empty string literal between them: + +```swift +text 1 2 3 // becomes "1 2 3" +text 1 "" 2 "" 3 // becomes "123" +``` + +**Note:** Spaces *between* values make no difference to the output, so feel free to space them as you wish. These are all equivalent: + +```swift +text "We're number "1"!" +text "We're number " 1"!" +text "We're number " 1 "!" +text "We're number " 1 "!" +``` + +## Wrap Width + +By default, text is laid out on a single line unless you explicitly add line-breaks, either by splitting the text into multiple literals or by adding `\n` escape sequences. + +But by using the `wrapwidth` option, you can force text to wrap automatically to fit a specified width. This is useful when working with programatically generated text, where it may be cumbersome to have to add logic to insert linebreaks at the right point. + +```swift +text { + wrapwidth 3 + "Hello, World!" +} +``` + +![Wrapped text](../../images/text-wrap.png) + +**Note:** `wrapwidth` is specified in world units, not characters. + +## Size and Line Height + +By default, text has a line height of one world unit. The line height is not the height of the actual characters, but the distance between two consecutive lines of text. If you imagine some lined writing paper, the line height would be the distance between the lines. + +To adjust the text size, you can use the [size](transforms.md#size) option: + +```swift +text { + size 2 // increase text size by 200% + "Hello, World!" +} +``` + +The size in this case actually refers to the line height, so setting a size of 2 increases the line height to 2 (and increases the actual size and spacing of the characters in proportion to this height). + +You can resize the text non-uniformly by passing separate width and height values for the size: + +```swift +text { + size 2 1.5 // set width to 200% and height to 150% + "Hello, World!" +} +``` + +Alternatively, for extruded text, you can set the size in the shape block instead, which allows you to also set the depth at the same time: + +```swift +extrude { + size 2 2 0.5 // 200% sized text, with 50% depth + text "Hello, World!" +} +``` + +## Line Spacing + +By default, text has a line spacing of zero, meaning that consecutive lines of text with size 1 will be spaced exactly one unit apart, with no extra padding. You can increase or decrease this line spacing using the `linespacing` property. A value of 0.5 will add an additional space of 0.5 world units between consecutive lines: + +```swift +text { + linespacing 0.5 + "Hello," + "World!" +} +``` + +![+50% Line spacing](../../images/linespacing-increased.png) + +A value of -0.5 will reduce the spacing by 0.5 (causing the lines to overlap by 0.5 units): + +```swift +text { + linespacing -0.5 + "Hello," + "World!" +} +``` + +![+50% Line spacing](../../images/linespacing-decreased.png) + +## Position and Orientation + +To adjust the text position and orientation, use the [position](transforms.md#position) and [orientation](transforms.md#orientation) commands: + +```swift +text { + position 2 1 // move text 2 units to the right and 1 unit up + orientation 0.5 // rotate by 90 degrees + "Hello World" +} +``` + +Or for filled or extruded text, you can set these on the containing shape block instead: + +```swift +fill { + position 1 2 3 // set the position in 3D space + orientation 0 0.25 0 // rotate around the Y axis + text "Hello World" +} +``` + +Unlike most shapes, which are positioned relative to their center, text is positioned relative to its left margin and baseline. + +![Default text alignment](../../images/text-default.png) + +In order to center a piece of text, you will need to know its actual dimensions. For this you can use the [bounds](bounds.md) member property. The following code uses the bounds to center some text: + +```swift +define hello text "Hello" +translate -hello.bounds.width/2 -hello.bounds.height/2 +fill hello +``` + +![Centered text](../../images/text-centered.png) + +## Font + +To adjust the text font, you can use the `font` command. like `color` and other [material](materials.md) properties, `font` can be placed either inside the `text` block, or anywhere before it in the same scope: + +```swift +font "Zapfino" +fill text "Hello World" +``` + +![Text with font](../../images/text-font.png) + +**Note:** Some fonts are inherently much more detailed than others, and may take a considerable time to generate. You may want to set the [detail](options.md#detail) option to a lower value for text than you would for other geometry. + +The font name you provide must match a font that is already installed on your system. If no matching fonts are found then an error will be raised. If you wish to load a font file directly, without installing it, you can pass the filename or path to the `font` command instead of the font name: + +```swift +font "filename.ttf" +``` + +Only fonts with a ".ttf", ".otf" or ".ttc" file extension are supported. The extension is required, or the `font` parameter will be treated as a system font rather than a file. If a relative path or filename is used, it should be specified relative to the ShapeScript file that references it. + +The filename can be constructed dynamically by using the [string interpolation](text.md#interpolation) feature, which is sometimes useful if, for example, you have multiple font files with a common prefix or suffix: + +```swift +for n in 1 to 5 { + texture "font" n ".ttf" + text "Hello, World!" + translate 0 -1 0 +} +``` + +--- +[Index](index.md) | Next: [Builders](builders.md) diff --git a/docs/1.7.1/ios/transforms.md b/docs/1.7.1/ios/transforms.md new file mode 100644 index 00000000..88ae6b44 --- /dev/null +++ b/docs/1.7.1/ios/transforms.md @@ -0,0 +1,110 @@ +Transforms +--- + +Every shape has a position, orientation and size. These can be set in *absolute* units by using the `position`, `orientation` and `size` options, or in *relative* units using the `translate`, `rotate` and `scale` commands (see [relative transforms](#relative-transforms) below). + +## Position + +When you define a shape in ShapeScript, it is created at the *point of origin*. By default, this is the zero position of the X, Y and Z axes within the current [scope](scope.md), but this can be overridden using the `position` option. + +The `position` option accepts a [vector](literals.md#vectors-and-tuples) of up to 3 values representing a coordinate along the X Y and Z axes. If values are omitted they are assumed to be zero. The default `position` of every shape is `0 0 0`, which is located at the *origin*, aka the center of the world. + +Positive `position` values move the shape right, up, and towards the camera respectively. Negative values move it left, down and away from the camera: + +```swift +cube { + position 0 0 5 // moves the cube 5 units towards the camera +} +``` + +Positions are applied hierarchically. For shapes located at the root of the ShapeScript file, their `position` is relative to the world origin, however you can nests shapes inside [groups](groups.md), in which case the `position` of the child shapes will be measured relative to the `position` of their containing group. + +## Orientation + +The `orientation` option defines the rotation for the shape using three parameters called `roll`, `yaw` and `pitch`. The `roll` value represents a rotation around the Z axis, the `yaw` is rotation around the Y axis, and the `pitch` is a rotation around the X axis: + +```swift +cube { + orientation 0 0.25 0 // rotates the cube 45 degrees around the Y axis +} +``` + +The ordering of these three parameters may seem counterintuitive (Z, Y, X), but it makes sense in the context that when working with 2D [paths](paths.md), you often wish to apply a rotation only around the Z axis (in the XY plane), and by having that as the first parameter, you can simply omit the other values, which will default to zero. + +Angles of rotation are specified as numbers in the range 0 to 2 (or 0 to -2 if you prefer), representing the number of [half-turns](https://en.wikipedia.org/wiki/Turn_(angle)). This again may seem odd if you were expecting degrees or radians, but using this range works better mathematically, as you avoid the need to multiply or divide computed rotation values by [pi](https://en.wikipedia.org/wiki/Pi) or 180, and it's easier to mentally convert 90 degrees to 0.5 than to 1.5707963268 (see the [trigonometry section](functions.md#trigonometry) for more about converting between angular representations). + +While it is relatively simple to use the `orientation` property to specify a rotation around a single axis, it can be awkward to apply a rotation around multiple axes at once due to the fixed order. If you need to do that, you may find it simpler to use the `rotate` command instead (described [below](#relative-transforms)), which can be applied multiple times in any order. + +## Size + +The `size` option applies a scaling factor to all subsequent geometry. A scale factor of `0.5 0.5 0.5` for example, would halve the size of all subsequent shapes, as well as halving the offset applied by subsequent `translate` commands. + +Like the `position` and `orientation` commands, `size` allows you to omit one or two parameters, however unlike the other commands, omitted parameters are assumed to be equal to the first value given. So `size 0.5` is equivalent to `size 0.5 0.5 0.5`. + +It it possible to apply a negative scale factor, which has the effect of flipping the geometry. For example, `size 1 -1 1` would flip all subsequent shapes upside-down along the Y axis. This does not always work as intended however, and may produce odd side-effects such as turning shapes inside-out. In general it is better to stick to positive `size` values, and use `orientation` if you need to flip a shape around. + +## Relative Transforms + +When defining paths or shapes procedurally using [loops](control-flow.md#loops) or other logic, you will often wish to position shapes or points using *relative* coordinates, rather than absolutely. You can do this using the `translate`, `rotate` and `scale` commands, which are counterparts to the `position`, `orientation` and `size` options. + +Translation is the mathematical term for directional movement. Like `position`, `translate` takes up to 3 values representing offsets along the X Y and Z axes. Unlike `position`, the values do not specify the position of the containing shape, but rather they move the *origin* of the current [scope](scope.md), affecting all subsequently defined shapes. + +The two following examples are therefore equivalent: + +```swift +cube { position 1 0 0 } +``` + +```swift +translate 1 0 0 +cube +``` + +In both cases, the cube is moved one unit to the right. But whereas in the first example the cube itself has been moved, in the second example, the *world* has been moved. + +This distinction doesn't matter much until you create another shape. In the first example, the effect of setting the `position` is limited to the cube itself, and subsequent shapes will be unaffected. However in the second example, all subsequent shapes will also be shifted by one unit to the right. + +You can prevent this by using another `translate` to move the origin back to its original position: + +```swift +translate 1 0 0 +cube // located at 1 0 0 +translate -1 0 0 +sphere // located at 0 0 0 +``` + +Just as the `translate` command moves the origin, the `rotate` command rotates it, and the `scale` command increases or decreases the scale factor. These are equivalent: + +```swift +cube { + size 2 + orientation 0.25 +} +``` + +```swift +scale 2 +rotate 0.25 +cube +``` + +And as with `translate`, in the second case the rotation and scale will be permanently altered for all future shapes, so to reset them you would need to apply the inverse transforms: + +```swift +scale 2 +rotate 0.25 +cube +rotate -0.25 +scale 0.5 +``` + +As mentioned in the [orientation](#orientation) section above, an advantage of the `rotate` command is that it allows you to apply rotations in any order. For example the following code applies a pitch of 45 degrees followed by a roll of 80 degrees, which would be very difficult to express as a single `rotate` or `orientation` instruction due to the fixed roll-yaw-pitch order: + +```swift +rotate 0 0 0.25 // pitch 45 degrees +rotate 0.4 0 0 // roll 80 degrees +cube +``` + +--- +[Index](index.md) | Next: [Bounds](bounds.md) diff --git a/docs/1.7.1/mac/blocks.md b/docs/1.7.1/mac/blocks.md new file mode 100644 index 00000000..f98315ee --- /dev/null +++ b/docs/1.7.1/mac/blocks.md @@ -0,0 +1,84 @@ +Blocks +--- + +A block is a nested list of instructions, contained inside `{ ... }` braces. Some commands, such as [builders](builders.md) or [CSG operations](csg.md), accept a block parameter instead of a simple value like a number or [vector](literals.md#vectors-and-tuples). + +Instructions inside a block are executed within the [scope](scope.md) of the command that invoked them. Typically that means that any transforms or material changes made inside the block will only apply to geometry created inside the same block. This also applies to any symbols that you define inside the block. + +You can define your own blocks using the `define` command. Here is a block that creates a five-pointed star: + +```swift +define star { + path { + for 1 to 5 { + point 0 -0.5 + rotate 1 / 5 + point 0 -1 + rotate 1 / 5 + } + point 0 -0.5 + } +} +``` + +You can call it by simply referencing its name, like this: + +```swift +star +``` + +![Star](../../images/star.png) + +**Note:** there is a subtle distinction between the code above and the code below: + +```swift +define star path { + for 1 to 5 { + point 0 -0.5 + rotate 1 / 5 + point 0 -1 + rotate 1 / 5 + } + point 0 -0.5 +} +``` + +In the original code, we defined a new block symbol that creates a star-shaped path. In the code above we've defined a symbol whose value is a star-shaped path. The former code is evaluated at the point when it is *called*, whereas the latter code is evaluated at the point when it is *defined*. + +The end-result is the same in this case, so it may seem like the distinction doesn't matter, but the advantage of the former approach is that we can add *options* to vary the behavior of the code when it is called. + +## Options + +To add an option to a block, you use the `option` command. This works in a similar way to the [define](symbols.md) command, but it allows the specified value to be overridden by the caller. + +The code below extends the `star` definition with options for the radius and number of points: + +```swift +define star { + option radius 1 + option points 5 + path { + for 1 to points { + point 0 -0.5 + rotate 1 / points + point 0 -radius + rotate 1 / points + } + point 0 -0.5 + } +} +``` + +Now we can use those options to create a star with 6 points if we choose: + +```swift +star { + points 6 + radius 2 +} +``` + +![Star](../../images/six-pointed-star.png) + +--- +[Index](index.md) | Next: [Scope](scope.md) diff --git a/docs/1.7.1/mac/bounds.md b/docs/1.7.1/mac/bounds.md new file mode 100644 index 00000000..cc5e3bbc --- /dev/null +++ b/docs/1.7.1/mac/bounds.md @@ -0,0 +1,115 @@ +Bounds +--- + +ShapeScript's [size](transforms.md#size) ands [scale](transforms.md#relative-transforms) commands let you control the relative size of a shape, but sometimes it's useful to know the exact dimensions. + +A cube of size 1 has an easily-predicted size of one world unit square, but what about a more complex shape, such as a 5-pointed star (see the [procedural paths](paths.md#procedural-paths) and [blocks](blocks.md) sections for details): + +```swift +define star path { + for 1 to 5 { + point 0 -0.5 + rotate 1 / 5 + point 0 -1 + rotate 1 / 5 + } + point 0 -0.5 +} + +// draw star +extrude { + color red + star +} + +// draw cube +cube { + color green 0.5 +} +``` + +![Star with unit cube](../../images/star-with-unit-cube.png) + +We can see that the star is larger than the unit cube, but other than trial-and-error or complex math, how can we get the exact size? This is where the `bounds` [member property](expressions.md#members) comes in. + +## Mesh Bounds + +Paths and meshes both expose a `bounds` property that represents a bounding box around the shape. From this you can get the exact size and position needed to place a box around the star: + +```swift +define star { + ... +} + +// define star shape +define shape extrude { + color red + star +} + +// draw star +shape + +// draw box around star +cube { + color green 0.5 + position shape.bounds.center + size shape.bounds.size +} +``` + +![Star with fitted cube](../../images/star-with-fitted-cube.png) + +## Path Bounds + +In the example above we computed the bounds of a solid `mesh` (an extruded star-shaped `path`) but you can also get the bounds of a `path` directly. The following code draws the star path inside its bounding rectangle: + +```swift +define star { + ... +} + +// draw star +star + +// draw rectangle around star +square { + position shape.bounds.center + size shape.bounds.size +} +``` + +![Star with fitted rectangle](../../images/star-with-fitted-rect.png) + +## Bounds Members + +The `bounds` member property has the following sub-properties that you can use: + +* `min` - The position of the corner of the box with the smallest X, Y and Z values relative to the origin. +* `max` - The position of the corner of the box with the largest X, Y and Z values relative to the origin. +* `center` - The position of the center of the box relative to the origin. +* `size` - The size (width, height and depth) of the box in world units. +* `width` - The width of the box along the X axis (equivalent to `size.width`) +* `height` - The height of the box along the Y axis (equivalent to `size.height`) +* `depth` - The depth of the box along the Z axis (equivalent to `size.depth`) + +So, for example, to get the height of a shape, you could use: + +```swift +print someShape.bounds.size.height +``` + +or just: + +```swift +print someShape.bounds.height +``` + +And to get the X coordinate of its rightmost edge you could use: + +```swift +print someShape.bounds.max.x +``` + +--- +[Index](index.md) | Next: [Meshes](meshes.md) diff --git a/docs/1.7.1/mac/builders.md b/docs/1.7.1/mac/builders.md new file mode 100644 index 00000000..ba0bafcb --- /dev/null +++ b/docs/1.7.1/mac/builders.md @@ -0,0 +1,277 @@ +Builders +--- + +We explored some basic 3D shape types in the [primitives](primitives.md) section, but the real power of ShapeScript comes from the ability to define custom shapes. + +In the [paths](paths.md) section we looked at how to define custom shapes using paths. ShapeScript has a variety of built-in commands for creating 3D shapes from paths, called *builders*: + +## Fill + +The most basic shape builder is the `fill` command, which creates a filled polygon from a path. Using the pentagon path we defined earlier, we can use `fill` to create a solid pentagon: + +``` +fill path { + for 0 to 5 { + point 0 1 + rotate 2 / 5 + } +} +``` + +![Filled Pentagon](../../images/filled-pentagon.png) + +Unlike a path, a filled shape can have a color and texture, but it has zero thickness. + +If a path contains multiple overlapping sub-paths, they will be filled using the [even-odd rule](https://en.wikipedia.org/wiki/Even–odd_rule). For example, the [overlapping circles](paths.md#nested-paths) path would be filled like this: + +```swift +fill path { + circle + translate 0.5 + scale 0.5 + circle +} +``` + +![Even-odd Fill](../../images/even-odd-fill.png) + +## Lathe + +The `lathe` command creates a 3D shape from a 2D path by revolving it around the Y axis. + +To use `lathe`, you must first define a suitable path. For example, the following code defines the *profile* (one half of the outline) of a chess piece: + +```swift +path { + curve 0 0.78 + curve -0.15 0.7 + curve -0.15 0.5 + point -0.07 0.45 + curve -0.12 0.2 + point -0.25 0.1 + point -0.25 0 + point 0 0 +} +``` + +![Pawn outline](../../images/pawn-profile.png) + +When passed to the `lathe` command, the path creates a solid 3D shape: + +```swift +lathe path { + curve 0 0.78 + curve -0.15 0.7 + curve -0.15 0.5 + point -0.07 0.45 + curve -0.12 0.2 + point -0.25 0.1 + point -0.25 0 + point 0 0 +} +``` + +![Pawn](../../images/pawn.png) + +As with other curved shapes such as the `sphere` and `cone`, the smoothness of the lathed surface can be controlled using the `detail` and `smoothing` commands. + +The path describing the profile of the lathed shape was in this case open-ended. Since the ends meet at the Y axis anyway, the resultant shape will still be closed. Open paths that do not touch the axis will produce a hollow shape with visible holes. + +Lathed paths must lie flat on the XY plane, and be positioned entirely to one side of the Y axis. Any points on the path with a non-zero Z coordinate will be flattened, and parts of the path that cross the Y axis will be clipped before lathing. + +## Extrude + +The `extrude` command extrudes a 2D path along the Z axis. The path therefore represents a cross-section of the final shape. The default extrusion distance is 1 unit, but it can be overridden using the `size` option or relative `scale` command (as described in [transforms](transforms.md)). + +In the following example, `extrude` is used to create a triangular prism: + +```swift +extrude { + size 1 1 2 // increase extrusion distance to 2 units + polygon { sides 3 } +} +``` + +![Prism](../../images/prism.png) + +You can also extrude a shape *along* another path using the `along` option. In the following example, a circle is extruded along a rounded rectangle: + +```swift +extrude { + circle + along roundrect { + size 5 + } +} +``` + +![Rounded rectangle with circle cross-section](../../images/roundrect-extrusion.png) + +In the following example, we extrude a square cross-section along a 3D path: + +```swift +define shape path { + orientation 0 0 -0.4 + curve 0 1 0.75 + curve -1 0 + curve 0 -1 0.25 + curve 1 0 + curve 1 1 + curve 0 1 0.75 +} + +extrude { + square { size 0.1 } + along shape +} +``` + +![Tangential extrusion](../../images/tangential-extrusion.png) + +Note how the cross-section tilts as it follows the path. This can be a nice effect, but you might prefer that the cross section remains perpendicular to the world axes. To control this, you can use the `axisAligned` property: + +```swift +extrude { + square { size 0.1 } + along shape + axisAligned true +} +``` + +![Axis-aligned extrusion](../../images/axis-aligned-extrusion.png) + +A value of `true` for `axisAligned` means the cross section will remain perpendicular to the path, which in some cases may cause the shape to look "pinched". + +A value of `false` means the cross-section is always aligned to the tangent of the path, which ensures consistent thickness at the cost of inconsistent orientation. + +If the `axisAligned` property is omitted, ShapeScript will try to choose an appropriate alignment for the path provided to `along`. + +To apply a twist along the length of an extruded shape, you can use the `twist` property: + +```swift +extrude { + square + twist 0.5 +} +``` + +![Twisted extrusion](../../images/twisted-extrusion.png) + +Twist uses the same [half-turn](transforms.md#orientation) units as the other rotation commands, so a twist of 0.5 equates to one quarter turn, or 90 degrees. + +Applying a twist will automatically increase the number of cross sections in proportion to the current [detail](options.md#detail) level to prevent distortion: + +![Twisted extrusion wireframe](../../images/twisted-extrusion-wireframe.png) + +You can combine `twist` with the `along` property to twist a curved path: + +```swift +extrude { + square { size 0.2 } + along circle + twist 2 +} +``` + +![Twisted circle](../../images/twisted-circle.png) + +**Note:** when extruding along a closed path, you should always use a multiple of `2` (i.e. a full rotation) for the `twist` value to avoid an ugly seam: + +![Twisted circle seam](../../images/twisted-circle-seam.png) + + +## Loft + +The `loft` command is similar to `extrude`, but instead of taking a single cross-section path and extruding it by a fixed distance, `loft` accepts multiple cross-sections and then joins them together to form a solid shape. + +For `loft` to work correctly, the specified paths must *not* all be located on the same plane, otherwise the resultant shape will be flat. You can either provide non-zero Z values for your path points, or use the `translate` command to space out your paths. + +For example, the following code produces a prism equivalent to the extrusion example above: + +```swift +loft { + // triangle 1 + polygon { sides 3 } + + translate 0 0 2 + + // triangle 2 + polygon { sides 3 } +} +``` + +You can also apply rotations and scaling between cross sections, to create bends or curves. For example, the following code creates a torus: + +```swift +define steps 32 +define radius 1 // radius of ring + +loft { + for 0 to steps { + circle { size 0.25 } + rotate 0 0 -1/steps + translate 0 0 (2 * pi * radius / steps) + rotate 0 0 -1/steps + } +} +``` + +![Torus](../../images/torus.png) + +However, the real power of the `loft` command comes from the fact that you can use *different* cross-sections, and the resultant surface will be interpolated between them. For example, here is a shape whose cross-section is square at one end and circular at the other: + +```swift +loft { + square + translate 0 0 1 + circle +} +``` + +![Loft shape](../../images/loftshape.png) + +## Hull + +The `hull` command works a bit like `loft`, in that it joins together multiple paths to form a solid surface. Here is the `loft` shape above, implemented using `hull`: + +```swift +hull { + square + translate 0 0 1 + circle +} +``` + +![Hull shape](../../images/loftshape.png) + +So if `hull` and `loft` are the same, why have a separate command? + +The difference is that rather than joining sections together in a tube, `hull` forms a tight *skin* around the outside of all its child shapes, known as a [convex hull](https://en.wikipedia.org/wiki/Convex_hull). To illustrate the difference, here is the `hull` of a star shape, vs a `loft` of the same shape: + +![Hull shape](../../images/hull-vs-loft.png) + +But the real power of the `hull` command is that it's not limited to operating on paths. Unlike the other builders, you can create a hull around meshes, paths, points, or any combination. For example, here is a hull formed from a cylinder and a point: + +```swift +hull { + cylinder + point 1 0 0 +} +``` + +![Hull shape](../../images/hull.png) + +This allows for a lot of interesting shapes that would be hard to create using the other commands. + +You might notice some odd stripes on the surface near the lip of the cylinder. As you may recall from the [detail](options.md#detail) section, curved surfaces in ShapeScript are actually formed from straight-edged polygons, with lighting used to make them appear smooth. + +When creating a hull from different shapes, the resultant [surface normals](https://en.wikipedia.org/wiki/Normal_(geometry)) aren't always what you'd expect, which can result in lighting glitches like this. In some cases you can use the [smoothing](options.md#smoothing) command to smooth out these discrepancies, like so: + +```swift +smoothing 0.25 +``` + +![Hull shape](../../images/smoothed-hull.png) + +--- +[Index](index.md) | Next: [Constructive Solid Geometry](csg.md) diff --git a/docs/1.7.1/mac/camera-control.md b/docs/1.7.1/mac/camera-control.md new file mode 100644 index 00000000..383666ad --- /dev/null +++ b/docs/1.7.1/mac/camera-control.md @@ -0,0 +1,44 @@ +Camera Control +--- + +When you open a file in ShapeScript, it is rendered in 3D using a virtual camera, which can be controlled either programmatically or via mouse or trackpad. + +## Camera Selection + +The camera defaults to "Front" view, which is positioned along the Z axis, looking forward towards the scene. The distance of the camera from the origin is set automatically based on volume occupied by the shapes in the scene. + +You can choose a different camera angle from the `View > Camera` menu. [Custom cameras](cameras.md#custom-cameras) can be defined programatically. ShapeScript will remember the last selected camera for each shape file. + +To temporarily change the camera position, you can alter the view using the mouse or trackpad (see below). To reset the current camera back to its original position, select the `View > Camera > Reset` menu, or press **Cmd-0** on the keyboard. + +## Mouse Control + +**Note:** The instructions below assume the use of an Apple Magic Mouse. Controls for other mice may differ. + +Motion | Action +:--------------------------- | :-------------------------- +Rotate scene | Hold the mouse button and drag +Pan up/down/left/right | Either swipe the mouse surface with one finger, or hold Shift + mouse button and drag +Zoom in and out | Hold Shift, then swipe up or down on the mouse surface with one finger + +
+ +## Trackpad Control + +Motion | Action +:--------------------------- | :-------------------------- +Rotate scene | Click and drag with one finger +Pan up/down/left/right | Swipe with two fingers +Zoom in and out | Either pinch, or hold Shift + swipe up or down with two fingers +Roll | Pinch, then rotate fingers + +
+ +## Copy Settings + +It can be difficult to visualize the effect that a given position or orientation will have when defining a [custom camera](cameras.md#custom-cameras). A good solution is to use touch gestures to position the camera, then select `View > Copy Camera Settings`, or press **Cmd-Shift-C** on the keyboard. + +This copies a snippet of ShapeScript code which you can then paste into your `.shape` file to create the custom camera. + +--- +[Index](index.md) | Next: [Primitives](primitives.md) diff --git a/docs/1.7.1/mac/cameras.md b/docs/1.7.1/mac/cameras.md new file mode 100644 index 00000000..f8a9f4dc --- /dev/null +++ b/docs/1.7.1/mac/cameras.md @@ -0,0 +1,132 @@ +Cameras +--- + +ShapeScript provides a number of built-in cameras for viewing your scene from the front, back, side, etc. But you may wish to view the scene from a custom viewpoint (say, a 45-degree overhead angle). + +While it is possible to reposition the view using the [camera controls](camera-control.md), it's difficult to do so in a reproducible way, which is especially important if you wish to [export](export.md) an image of your scene. + +## Custom Cameras + +To solve this, in addition to the built-in cameras, ShapeScript also provides a syntax for defining your own cameras. To define a custom camera, use the `camera` command: + +```swift +camera +``` + +The location of the camera in the file is mostly unimportant - it will behave the same if it is placed before or after the visible geometry. Cameras added to your `.shape` file will appear in the camera menu below the other cameras. + +![Custom camera menu](../../images/custom-camera-menu.png) + +You may add multiple cameras to a scene and they will be named "Custom", "Custom 2", "Custom 3", and so on. You can use the `name` command to assign a more meaningful name to a given camera, and this will be reflected in the menu: + +```swift +camera { + name "Hello" +} +``` + +You can configure the camera position and orientation programmatically using the properties described below, however the easiest way to define a custom camera is to position it using the camera controls and then [copy the configuration](camera-control.md#copy-settings) and paste it into your `.shape` file. + +## Position + +By default the camera will be positioned just in front of the scene, but you can adjust its position using the `position` property: + +```swift +camera { + position 3 3 3 +} +``` + +This code creates a camera at the coordinates `3,3,3`, which is diagonally up, right, and forwards from the center of the scene. Since we have not specified otherwise, the camera will point in the direction of the center of the scene, so you might see something like this: + +![Custom camera](../../images/custom-camera.png) + +## Orientation + +To change the orientation of the camera, and an `orientation` property: + +```swift +camera { + position 3 3 3 + orientation 0 -0.15 0.25 +} +``` + +This rotates the camera by 27 degrees to the left (`0.15 * 180`) and 45 degrees down (`0.25 * 180`), so that it is pointing roughly at the cone: + +![Custom camera orientation](../../images/camera-orientation.png) + +Cameras are also affected by [relative transforms](transforms.md#relative-transforms), so the following would be equivalent to the above: + +```swift +translate 3 3 3 +rotate 0 -0.15 0.25 +camera { + position 0 + orientation 0 +} +``` + +**Note:** the `position 0` and `orientation 0` here are required, because if these are not specified then the camera position and orientation will be set to their default values. + +## Field of View + +In addition to position and orientation, you can also adjust the *field of view* using the `fov` property: + +```swift +camera { + position 3 3 3 + orientation 0 -0.15 0.25 + fov 0.25 +} +``` + +If you imagine your view as a cone expanding out from the camera, the field of view is the angle between the top and bottom edges of the cone. Like all angles in ShapeScript, it's measured in multiples of 180 degrees, so `fov 0.25` sets the field of view to 45 degrees - slightly narrower than the default of 60 degrees. This has the effect of *zooming-in* the camera: + +![Custom field of view](../../images/custom-fov.png) + +You might think that the same effect could be achieved by simply moving the camera closer to the center of the scene, but in practice the result of this looks quite different due to the effect of perspective. To eliminate the effect of perspective, you can use an *orthographic* camera, which displays the scene without any perspective distortion. + +## Orthographic View + +To use an orthographic camera, you can either toggle the `Orthographic` menu (which affects all cameras), or you can set `fov` to zero or a negative value in your script, which will override the global setting. + +![Orthographic view](../../images/orthographic-camera.png) + +When using an orthographic projection, the distance of the camera from an object no longer affects its size on screen. In order to control the scale at which objects appear, you can use the `size` property of the camera: + +```swift +camera { + position 3 3 3 + orientation 0 -0.15 0.25 + size 5 +} +``` + +As with `position` and `orientation`, if the `size` property is omitted then a suitable orthographic scale will be determined automatically. + +## Background + +The [background color or texture](commands.md#background) for the scene can be overridden for each camera. This opens up some useful possibilities such as setting orthographic sketches as background for different cameras to use as reference images when modeling: + +```swift +camera { + name "Side View" + position 5 0 0 + background "side-view.jpg" +} +``` + +## Pixel Dimensions + +You can add an optional `width` and/or `height` to your camera block to control the pixel dimensions of the captured image. This has no effect on how the camera view is displayed in the app, but determines the size of [exported images](export.md#image-formats): + +```swift +camera { + width 1024 + height 768 +} +``` + +--- +[Index](index.md) | Next: [Comments](comments.md) diff --git a/docs/1.7.1/mac/commands.md b/docs/1.7.1/mac/commands.md new file mode 100644 index 00000000..80adec96 --- /dev/null +++ b/docs/1.7.1/mac/commands.md @@ -0,0 +1,122 @@ +Commands +--- + +Like [options](options.md), and [functions](functions.md), commands in ShapeScript are denoted by a keyword followed by zero or more values or expressions. Different commands accept different value types, but typically these will be a number, vector, text or [block](blocks.md). + +```swift +detail 5 // a numeric argument + +translate 1 0 -1 // a vector argument + +texture "world.png" // a text argument +``` + +The values passed to a command can be anything from simple literal values, to symbol references, to whole expressions. For example: + +```swift +rotate 1 + angle 0 0 +``` + +In the case above, the `roll` parameter of the `rotate` command has been given a value of `1 + angle`, and the `yaw` and `pitch` parameters are zero. Expressions can be a bit confusing to read inside a tuple of parameters due to the differing significance of spaces between values, so for clarity you may wish to add parentheses: + +```swift +rotate (1 + angle) 0 0 +``` + +Commands do not *necessarily* accept or return a value. The main distinction between commands and functions/constants is that commands have *side effects*. Typically they either alter the appearance of the shape, or alter the effects of subsequent commands. Here are some examples: + +ShapeScript has a number of built-in commands: + +## Background + +The `background` command can be used to set the background color of the rendered scene. For example: + +```swift +background 1 0 0 // set background color to red +``` + +Instead of a color, you can pass a file path to an image: + +```swift +background "filename.png" // set background image +``` + +This affects how the scene appears in the ShapeScript viewer, as well as the background of [exported images](export.md#image-formats). By default, the background is transparent. + +**Note:** the `background` command can only be used at the root level of a ShapeScript file, or inside a [camera](cameras.md) node: + +## Detail + +The `detail` command can be used anywhere in the ShapeScript file to override the local detail level used for approximating curved geometry. It is documented in the [options](options.md#detail) section. + +## Smoothing + +The `smoothing` command can be used anywhere in the ShapeScript file to override the local smoothing level used for calculating surface normals. It is documented in the [options](options.md#smoothing) section. + +## Materials + +The `color`, `texture` and `opacity` commands are used to specify the appearance of shapes when rendered. These commands are documented in the [materials](materials.md) section. + +## Font + +The `font`, command is used to specify the font used to render [text](text.md). + +## Transforms + +The `rotate`, `translate` and `scale` commands are useful for procedurally generating paths and complex shapes. These are documented in the [transforms](transforms.md#relative-transforms) section. + +## Primitives + +The `cube`, `sphere`, `cone` and `cylinder` commands are used to generate simple 3D shapes that can be composed into more complex forms. They are documented in the [primitives](primitives.md) section. + +## Paths + +The `path`, `circle` and `square` commands are used to create paths that can be used as the inputs for `builder` commands that can generate complex 3D shapes. They are documented in the [paths](paths.md) section. + +## Text + +The `text` command is used to generate individual words, lines or paragraphs of text, which can then be [filled](builders.md#fill) or [extruded](builders.md#extrude) to create a 3D mesh. The `text` command is documented in the [text](text.md) section. + +## Builders + +The `fill`, `lathe`, `extrude` and `loft` commands turn paths into 3D meshes. They are documented in the [builders](builders.md) section. + +## Constructive Solid Geometry + +The `difference`, `union`, `intersection` and `stencil` commands use boolean operations to merge or subtract shapes from each other to form surfaces that would be hard to model directly. They are documented in the [CSG](csg.md) section. + +## Random Numbers + +The `rnd` and `seed` commands can be used to generate pseudorandom values that are great for procedurally generating natural-looking shapes. The [train example](examples.md#train) uses this approach to create a jumbled layer of coal behind the driver's cab. + +The `rnd` command takes no parameters but returns a random number in the range 0 to 1. It can be used as one of the inputs to a `rotate` or `translate` command, or multiplied by other values as part of an expression to produce random numbers in different ranges: + +```swift +// randomly position a cube between -5 and +5 on the y axis +cube { position 0 (rnd * 10) - 5 0 } +``` + +Each time `rnd` is called it will return a different value. Numbers are returned in a deterministic but non-repeating sequence. Because the sequence is deterministic, it will always produce the same values each time your scene is rendered. + +To alter the random sequence you can use the `seed` command. The `seed` command takes a numeric value as its argument, and this is used to generate all subsequent `rnd` values. The seed value can be any number (positive or negative, integer or fraction), but note that values outside the range 0 to 232 will be wrapped to that range. + +If you are not happy with how some randomly generated geometry looks, try setting the seed to an arbitrary value, and keep tweaking it until you like the result: + +```swift +seed 57 +``` + +You can reset the `seed` at any point within your ShapeScript file, and it will alter the sequence for subsequent `rnd` calls. Like most other commands, `seed` is scoped, so setting the `seed` inside a [group](groups.md) or [block](blocks.md) will only apply to `rnd` calls within that block, and `rnd` commands after the closing `}` will use the previously-specified `seed` value. + +The default starting value for `seed` is zero, so `seed 0` will cause the `rnd` sequence to repeat from the beginning. Remember that the sequence produced from a given seed is always the same, so re-using the same seed value multiple times in your script will result in repetition of the same random sequence. + +## Debugging + +The `debug`, `print` and `assert` commands can be used to understand what's happening in your script and help diagnose problems. These are documented in the [debugging](debugging.md) section. + +## Import + +The `import` command is used to import an external ShapeScript or 3D geometry file. For more details on how this is used, see the [import](import.md) section. + +--- +[Index](index.md) | Next: [Control Flow](control-flow.md) diff --git a/docs/1.7.1/mac/comments.md b/docs/1.7.1/mac/comments.md new file mode 100644 index 00000000..ae20adb0 --- /dev/null +++ b/docs/1.7.1/mac/comments.md @@ -0,0 +1,67 @@ +Comments +--- + +Any content that follows a `//` (double slash) is treated as a comment. Comments can appear at the start of a line, or at the end. The comment terminates at the next line-break. + +Comments can be used to document individual lines of code, or whole blocks. For example: + +```swift +color 1 0 0 // red + +// this code draws a triangle +fill path { + for 0 to 3 { + point 0 1 + rotate 2 / 3 + } +} +``` + +Comments are also useful for temporarily disabling a block of code when debugging a scene. Some editors (such as Xcode) allow you to comment or uncomment multiple lines of code at once by making a multi-line selection and then pressing **Cmd-/** on the keyboard. + +## Block Comments + +Another way to disable a large chunk of code at once is to use a *block comment*. Block comments begin with `/*` and end with `*/`. Anything between these delimiters is considered part of the comment, even if they span multiple lines: + +```swift +/* +// this code is disabled +fill path { + for 0 to 3 { + point 0 1 + rotate 2 / 3 + } +} +*/ +``` + +Block comments can also be useful if you want to place a comment in the middle of a line, for example: + +```swift +define a(b c d) { + b /* + c */ + d +} +``` + +Here we've excluded the `c` parameter from the result, however the code both before and after the comment is unaffected. + +## Nested Comments + +Unlike some other languages, ShapeScript allows block comments to be nested inside each other. This is useful if you want to comment out some code that already contains comments: + +```swift +/* +define foo { + option bar 3 + /* option baz 5 */ + + // return bar + print bar +} +*/ +``` + +**Note:** although nested comments are supported by ShapeScript itself, they may confuse the syntax highlighting in whichever editor you are using. Don't be alarmed if part of the comment appears in the wrong color. + +--- +[Index](index.md) | Next: [Literals](literals.md) diff --git a/docs/1.7.1/mac/control-flow.md b/docs/1.7.1/mac/control-flow.md new file mode 100644 index 00000000..864e06f1 --- /dev/null +++ b/docs/1.7.1/mac/control-flow.md @@ -0,0 +1,175 @@ +Control Flow +--- + +## Loops + +To repeat an instruction (or sequence of instructions) you can use a `for` loop. The simplest form of the for loop takes a [numeric range](expressions.md#ranges), and a block of instructions inside braces. The following loop creates a circle of 5 points (you might use this inside a `path`): + +```swift +for 1 to 5 { + point 0 1 + rotate 2 / 5 +} +``` + +The range `1 to 5` is inclusive of both the start and end values. A range of `0 to 5` would therefore loop *6* times and not 5 as you might expect. + +The loop range does not have to be a literal value, you can use a previously defined symbol or expression instead: + +```swift +define count 5 + +for 1 to count { + point 0 1 + rotate 2 / count +} +``` + +## Loop Index + +If you have used similar loops in other programming languages, you might be wondering why we don't need to use an index variable of some kind to keep track of the loop iteration? + +Symbols defined inside the `{ ... }` block will not persist between loops (see [scope](scope.md) for details), but changes to the world transform will, which is why the `rotate` command doesn't need to reference the index - its effect is cumulative. + +If you *do* need to reference the index inside your loop for some reason, you can define a loop index symbol like this: + +```swift +for i in 1 to count { + point 0 i +} +``` + +This defines a [symbol](symbols.md) called `i` with the value of the current loop iteration. The `i` symbol only exists within the loop body itself and can't be referenced after the loop has ended. + +**Note:** The index symbol does not need to be called `i`, it can be any valid symbol name that you choose. + +If you want to loop in increments greater or less than 1, you can use the optional `step` property: + +```swift +for i in 1 to 5 step 2 { + print i // prints 1, 3, 5 +} + +for i in 0 to 1 step 0.2 { + print i // prints 0, 0.2, 0.4, 0.6, 1 +} +``` + +If not specified, the `step` value defaults to 1. + +## Looping Backwards + +If the end value of a loop is less than the start value, the loop body will normally be skipped, but if you do wish to loop backwards you can achieve this by using a negative step value: + +```swift +for i in 5 to 1 step -1 { + print i // prints 5, 4, 3, 2, 1 +} +``` + +## Looping Over Values + +As well as looping over a numeric range, you can also loop over a tuple of values: + +```swift +define values 1 5 7 9 + +for i in values { + print i // prints 1 5 7 9 +} +``` + +The values can be non-numeric, or even a mix of different types: + +```swift +define values "Mambo" "No." 5 + +for i in values { + print i // prints Mambo No. 5 +} +``` + +**Note:** To use a list of values directly in the loop definition, they must be placed in parentheses: + +```swift +for i in ("parentheses" "are" "required") { + print i +} +``` + +## If-Else + +Sometimes instead of repeating an action multiple times you need to perform it just once, but conditionally, based on some programmatic criteria. For example, your scene might have multiple configurations that you can switch between by setting constants at the top of the file. + +To execute code conditionally, you can use an `if` statement: + +```swift +define showCube true + +if showCube { + cube +} +``` + +The `showCube` constant here is a [boolean](https://en.wikipedia.org/wiki/Boolean_data_type) that can have the value `true` or `false`. The condition for an `if` statement must always be a boolean expression. The body of the `if` statement will only be executed if the condition is true. + +To perform an alternative action for when the condition is false, you can add an `else` clause: + +```swift +if showCube { + cube +} else { + sphere +} +``` + +You can chain multiple conditional statements using the `else if` construct: + +```swift +if showCube { + cube +} else if showSphere { + sphere +} else if showCone { + cone +} else { + print "Nothing to see here!" +} +``` + +## Conditional Defines + +Something you might want to do with an `if` statement is to conditionally define a constant value, for example: + +```swift +define highlighted true + +if highlighted { + define cubeColor red +} else { + define cubeColor white +} + +cube cubeColor +``` + +Unfortunately this won't work, due to the [scope](scope.md) rules. The `cubeColor` symbol is only defined inside the `if` statement blocks themselves, and can't be accessed outside. So how can you set the value of `cubeColor` conditionally? + +The solution is to move the `if` statement *inside* the `define` itself, like this: + +```swift +define highlighted true + +define cubeColor { + if highlighted { + red + } else { + white + } +} + +cube { color cubeColor } +``` + +--- +[Index](index.md) | Next: [Blocks](blocks.md) diff --git a/docs/1.7.1/mac/csg.md b/docs/1.7.1/mac/csg.md new file mode 100644 index 00000000..fb5a64e6 --- /dev/null +++ b/docs/1.7.1/mac/csg.md @@ -0,0 +1,100 @@ +Constructive Solid Geometry +--- + +Constructive Solid Geometry (CSG) is a process by which solid shapes can be combined or subtracted from one another to create shapes that would be hard to model directly. + +There are four basic types of CSG command in ShapeScript: + +## Difference + +The `difference` command takes 2 or more child meshes and subtracts them from one another. In this example, a green-colored cylinder is subtracted from a red cone: + +```swift +difference { + cone { + color 1 0 0 + } + cylinder { + position 0 -0.1 0 + orientation 0.5 + size 0.5 1 + color 0 1 0 + } +} +``` + +The result is shown below on the right. The shape on the left is using a `group` instead of `difference`, so you can see the position of the cylinder prior to subtraction. + +![Cone with a hole](../../images/group-vs-difference.png) + +The subtracted shape is removed from the result, but leaves an impression in the shape it is subtracted from. You can see this in above image, where the hole in the red cone is lined with green coloring from the cylinder. + +## Intersection + +The `intersection` command returns a shape representing the common volume between all input shapes. You can think of it as being like the *AND* function from boolean algebra. + +If we change the `difference` to an `intersection` in the previous code sample, this is the result: + +![Intersection of cone and cylinder](../../images/intersection.png) + +As before, the color is taken from the shape that originally contributed that part of the surface. + +## Union + +The `union` command merges its child meshes into a single shape. You can think of it as being like the *OR* function from boolean algebra. + +For an opaque shape the result is not visually distinguishable from simply using a `group`, but below the surface the `union` will split intersecting polygons and remove internal faces so that only the outer shell remains. + +This can be important for rendering efficiency, as well as producing more predictable results if the mesh is then used for additional CSG operations. + +The effect can be seen by using a translucent color (one with an alpha component less than one). In the image below, the red cones have been set to an alpha opacity of 0.75, with the green cylinders left fully opaque. + +The shape on the left is using a `group`, and the inner surface of the cylinder can clearly be seen through the surface of the cone. The shape on the right is using `union` and so the part of the cylinder inside the cone cannot be seen (because it has been removed). + +![Group vs union](../../images/group-vs-union.png) + +## XOR + +The `xor` command (short for *Exclusive-OR*) combines its child objects using the [even-odd rule](https://en.wikipedia.org/wiki/Even–odd_rule). For example, two overlapping cylinders will produce an eye-shaped hole where the volumes intersect: + +```swift +xor { + cylinder { + color 1 0 0 + } + cylinder { + position 0.5 + color 0 1 0 + } +} +``` + +![Eye-shaped Hole](../../images/xor.png) + +## Stencil + +The `stencil` command retains the shape of its first child, but "paints" the intersecting areas between the child shapes using the material of the other children. This can be used to apply logos or patterns to the surface of a shape. + +In the following example, a red ball has been stenciled with the pattern of a green square: + +```swift +stencil { + // ball + sphere { color 1 0 0 } + // square + cube { + color 0 1 0 + size 0.4 + position 0 0 0.5 + } +} +``` + +![Stenciled ball](../../images/stencil.png) + +**Note:** If the stencil shape has a texture applied to it (see [Materials](materials.md#texture)) then the texture wrapping will match the coordinates of the shape being painted, not the shape that the texture is taken from. + +If the child shapes both have the same color or texture, there will be no visible effect from using stencil. + +--- +[Index](index.md) | Next: [Groups](groups.md) diff --git a/docs/1.7.1/mac/debugging.md b/docs/1.7.1/mac/debugging.md new file mode 100644 index 00000000..98ee5535 --- /dev/null +++ b/docs/1.7.1/mac/debugging.md @@ -0,0 +1,97 @@ +Debugging +--- + +When using the [constructive solid geometry](csg.md) operations it can sometimes be difficult to visualize the individual components that comprise a given shape. In the example below, a moon shape is created by subtracting one cylinder from another: + +```swift +difference { + cylinder { + size 1 0.2 + } + + // cylinder not visible in viewer + cylinder { + position 0.4 + size 1 0.2 + } +} +``` + +![Moon](../../images/moon.png) + +Because the second cylinder is subtracted from the first, we can't actually see it, which means we can't [select it](getting-started.md#debugging-and-selection) to get information about it, or properly understand its shape. To solve this, we can use the `debug` command: + +```swift +difference { + cylinder { + size 1 0.2 + } + + // debug command makes cylinder visible + debug cylinder { + position 0.4 + size 1 0.2 + } +} +``` + +This makes the cylinder visible in the ShapeScript viewer and allows it to be selected like any other shape. + +![Moon debug](../../images/moon-debug.png) + +## Logging + +When creating complex scripts, it can sometimes be difficult to understand what's happening in the code. To help you debug your scripts, you can use the `print` command: + +```swift +print 5 + 6 + +print "some text" + +print someValue +``` + +The `print` command accepts one or more arguments of any type. You can use this to intersperse values and text labels for example: + +```swift +print "width =" width + +print "x:" x "y:" y +``` + +Printed values are displayed in a console area below the scene. The console can be resized and scrolled to show as much text as you need. + +## Assertions + +Rather than merely printing a value, sometimes you want to be certain that it has a particular value (or range of values). You can do that with the `assert` command: + +```swift +assert color = red // no other color will do +``` + +The assert function accepts a single boolean value or expression, and will raise an error if it evaluates to false. + +So when would this be useful? Suppose that you have defined a block, like the [star example](blocks.md#options) and you want a way to specify that it must have at least 4 points and a nonzero radius. You could do that like this: + +```swift +define star { + option radius 1 + assert radius > 0 + option points 5 + assert points >= 4 + path { + for 1 to points { + point 0 -0.5 + rotate 1 / points + point 0 -radius + rotate 1 / points + } + point 0 -0.5 + } +} +``` + +Now, if you (or someone else) tries invoke `star` with invalid options, it will raise a meaningful error instead of just producing broken-looking geometry. + +--- +[Index](index.md) | Next: [Import](import.md) diff --git a/docs/1.7.1/mac/examples.md b/docs/1.7.1/mac/examples.md new file mode 100644 index 00000000..39deab1e --- /dev/null +++ b/docs/1.7.1/mac/examples.md @@ -0,0 +1,55 @@ +Examples +--- + +ShapeScript includes a number of example files that demonstrate various features. You can find these under the `Help > Examples` menu. + +## Ball + +The Ball example demonstrates how to use the [stencil](csg.md#stencil) command to "paint" patterns on a sphere, as well as a [for loop](control-flow.md#loops) to generate a star shape that is then [extruded](builders.md#extrude). + +![Ball](../../images/ball.png) + +## Chessboard + +The Chessboard example demonstrates the use of [for loops](control-flow.md#loops) to duplicate shapes, along with [paths](paths.md), [lathe builders](builders.md#lathe) and [CSG](csg.md) operations. + +![Chessboard](../../images/chessboard.png) + +## Cog + +The Cog example demonstrates procedural generation of a complex [path](paths.md) using a [for loop](control-flow.md#loops), as well as the creation of a custom [block](blocks.md) and the use of the [option](blocks.md#options) command to pass parameters. + +![Cog](../../images/cog.png) + +## Earth + +The Earth example demonstrates use of the [texture](materials.md#texture) command to turn a simple sphere into a model of the globe. + +![Earth](../../images/earth.png) + +## Icosahedron + +The Icosahedron demonstrates the use of the [mesh command](meshes.md) along with [structured data](literals.md#structured-data) and [custom functions](functions.md#custom-functions) to create a complex shape that cannot easily be derived from the built-in primitives. + +![Icosahedron](../../images/icosahedron.png) + +## Spirals + +The Spirals example demonstrates using the [extrude](builders.md#extrude) command to create a spiral. This example also demonstrates the use of [for loops](control-flow.md#loops) and user-defined [options](blocks.md#options). + +![Spirals](../../images/spirals.png) + +## Spring + +The Spring example demonstrates how to use the [loft](builders.md#loft) command and a [for loop](control-flow.md#loops) to create a coiled spring shape. + +![Spring](../../images/spring.png) + +## Train + +The Train demonstrates a combination of modeling techniques, including using the [rnd](commands.md#random-numbers) command to generate pseudo-random coal. + +![Train](../../images/train.png) + +--- +[Index](index.md) | Next: [Glossary](glossary.md) diff --git a/docs/1.7.1/mac/export.md b/docs/1.7.1/mac/export.md new file mode 100644 index 00000000..02953244 --- /dev/null +++ b/docs/1.7.1/mac/export.md @@ -0,0 +1,82 @@ +Export +--- + +Once you've finished crafting your 3D scene, you'll probably want to *do something* with it. For that you will need to use the *Export* feature. + +**Export is a paid upgrade that can be unlocked via in-app purchase in the [ShapeScript Mac App](https://apps.apple.com/app/id1441135869). Export is not available in the free ShapesScript Viewer.** + +To export your scene, select the `File > Unlock Export Feature…` menu (**Cmd-Shift-E**) to unlock the export functionality. Once unlocked, this menu will change to `Export…`. + +**Note:** If the `Export…` menu is grayed-out, it is most likely because your scene is still loading. Wait for the loading spinner in the top-left of the ShapeScript document window to finish before trying to export. + +![Loading](../../images/loading.png) + +## Export Formats + +ShapeScript can export your scene in a variety of model formats, selectable from the export window: + +Extension | File Type | Supports All Features +:-------------------- | :------------------------------------------------|:------------------------------ +abc | Alembic | No +dae | Collada DAE | Yes +obj | Wavefront Object | No +scn / scnz | SceneKit Scene Document | Yes +usd / usdz | Universal Scene Description | No +ply | Polygon File Format | No +stl | Stereolithography | No + +
+ +**Note:** Not all formats support all features of ShapeScript scenes, so you may need to experiment. In general, DAE is the most reliable, widely-supported format to use. + +Some model formats do not support embedding geometry and textures or materials in a single file; In this case, ShapeScript will export a folder containing the model and associated assets as separate files. + +Exported models can be used in a variety of ways: + +## Games and AR + +Models exported from ShapeScript are well-suited to use in realtime 3D because the `detail` command gives you fine control over the triangle count. For realtime use you should generally set the detail level as low as you can get away with. + +You can import DAE files into a game development tool like Unity, or use USD(Z) files with Apple's SceneKit and RealityKit frameworks in Xcode. + +## 3D Printing + +ShapeScript can export models in Stereolithography (STL) format, used by many 3D printing applications. + +When exporting for 3D printing, you will usually want to avoid having internal geometry inside the outer surface of your model. A good way to do this is to use the [union](csg.md#union) command to combine all the parts of your model into a single shape, eliminating internal faces. + +ShapeScript scenes use the "Y-up" convention, where the Y-axis points up and the Z-axis points out from the screen. Some popular 3D printing applications such as [Cura](https://ultimaker.com/software/ultimaker-cura) use the "Z-up" convention instead. Check the "Convert to Z-Up" option in the export window to export your model in this orientation. + +## Image Formats + +In addition to 3D model formats, ShapeScript can also export 2D images. By default, images will be captured using the current camera, but you can select a different [camera view](cameras.md) from the export window. + +The following image formats are supported: + +Extension | File Type | Supports Transparency +:---------------------| :-------------------------------------------------|:------------------------------ +gif | Graphics Interchange Format | Yes +png | Portable Network Graphics | Yes +jpg / jpeg | Joint Photographic Experts Group | No +jpf / jp2 | JPEG 2000 | Yes +tif / tiff | Tagged Image File Format | Yes +bmp | Bitmap | No + +
+ +If you aren't sure which format to use, the PNG format is a good all-rounder, with lossless compression and transparency support. + +## Image Options + +The size of the exported image defaults to the current window size at the current display resolution. You can override this size in your script file by adding `width` and/or `height` options to your [custom camera](cameras.md#pixel-dimensions), or by clicking on the size label in the export window and selecting a different size: + +![Export size](../../images/export-size.png) + +Images are exported with a transparent background by default if the selected format supports it, or white otherwise. To change the background color or set a background image, you can use the [background command](commands.md#background). + +When exporting an image (or exporting a model for non-realtime use), you may wish to use the `detail` command to increase the detail level. A detail level of 100 should be good enough for even a very large or high-resolution image, but this may take a long time to generate for a complex model. + +**Note:** Although ShapeScript can export images, for best results you should export as a 3D model and then import that into a [ray tracing](https://en.wikipedia.org/wiki/Ray_tracing_(graphics)) program that provides fine-grained control over scene lighting and camera placement. + +--- +[Index](index.md) | Next: [Examples](examples.md) diff --git a/docs/1.7.1/mac/expressions.md b/docs/1.7.1/mac/expressions.md new file mode 100644 index 00000000..033fe309 --- /dev/null +++ b/docs/1.7.1/mac/expressions.md @@ -0,0 +1,313 @@ +Expressions +--- + +Rather than pre-calculating the sizes and positions of your shapes, you can get ShapeScript to compute the values for you using *expressions*. + +Expressions are formed by combining [literal values](literals.md), [symbols](symbols.md) or [functions](functions.md) with *operators*. + + +## Operators + +Operators are used in conjunction with individual values to perform calculations: + +```swift +5 + 3 * 4 +``` + +ShapeScript supports all the standard [infix](https://en.wikipedia.org/wiki/Infix_notation) arithmetic operators: + +Symbol | Name | Function +:------------- | :-------------------- | :-------------------------------------------------------------------- ++ | plus | Adds the left and right values +‐ | minus | Subtracts the right value from the left value +* | times | Multiplies the left value by the right value +/ | divide | Divides the left value by the right value +% | modulo | Remainder of dividing the left value by the right value + +
+ +Unary + and - are also supported: + +```swift +-5 * +7 +``` + +Operator precedence follows the standard [BODMAS](https://en.wikipedia.org/wiki/Order_of_operations#Mnemonics) convention, and you can use parentheses to override the order of evaluation: + +```swift +(5 + 3) * 4 +``` + +Because spaces are used as delimiters in [vector literals](literals.md#vectors-and-tuples), you need to take care with the spacing around operators to avoid ambiguity. Specifically, unary + and - must not have a space after them, and ordinary infix operators should have balanced space around them. + +For example, these expressions would both evaluate to a single number with the value 4: + +```swift +5 - 1 +5-1 +``` + +Whereas this expression would be interpreted as a 2D vector of 5 and -1: + +```swift +5 -1 +``` + +## Equality and Comparison + +ShapeScript includes the following equality and comparison operators, which can be used in [conditional logic](control-flow.md#if-else): + +Symbol | Name | Function +:------------- | :-------------------- |:-------------------------------------- += | equal | Compares two values and returns `true` if they are equal +<> | not equal | Compares two values and returns `false` if they are equal +< | less than | Returns `true` if the left value is less than the value on the right +<= | less than or equal | Returns `true` if the left value is less than or equal to the right +> | greater than | Returns `true` if the left value is greater than the value on the right +>= | greater than or equal | Returns `true` if the left value is greater than or equal to the right + +
+ +**Note:** You may have used other languages where `=` is written as `==`. This is generally because in such languages the `=` operator is used for assignment, and re-using the same symbol would cause ambiguity. This is not a problem in ShapeScript. + +While these operators are typically used with numeric inputs, the *output* is a boolean value (`true` or `false`). These values are most commonly used in conjunction with with the `if/else` control flow statement. For example: + +```swift +if rnd > 0.5 { + print "heads" +} else { + print "tails" +} +``` + +But they can also be assigned to a symbol and passed around: + +```swift +define averageColor (color.red + color.green + color.blue) / 3 +define isBrightColor averageColor >= 0.5 +print isBrightColor // prints true or false +``` + +## Linear Algebra + +As well as operating on individual numbers, some operators can be used with [vectors or tuples](literals.md#vectors-and-tuples). To multiply or divide a tuple of numbers by a scalar value you can use: + +```swift +define numbers (1 2 3 -4) +print numbers * 2 // prints 2 4 6 -8 +``` + +You can also multiply two tuples: + +```swift +define left (1 2 3) +define right (1 -2 3) +print left * right // prints 1 -4 9 +``` + +Note that is a simple member-wise multiplication of the numbers. For other types of vector multiplication such as the dot or cross product see the [functions section](functions.md#linear-algebra). + +If the tuples have different lengths, the result will be truncated to the shorter of the two: + +```swift +define left (1 2 3) +define right (1 -2) +print left * right // prints 1 -4 +``` + +You can also add or subtract two lists of numbers together: + +```swift +define left (1 2 3) +define right (1 -2 3) +print left + right // prints 2 0 6 +``` + +Unlike with multiplication or division, adding or subtracting a shorter tuple from a longer one will preserve the length of the left side: + +```swift +define left (1 2 3) +define right (1 -2) +print left + right // prints 2 0 3 +``` + +Adding a longer tuple to a shorter one will not widen the result however: + +```swift +define left (1 2 3) +define right (1 -2 3 4) +print left + right // prints 2 0 3 +``` + +## Boolean Algebra + +Along with the standard arithmetic operators, ShapeScript also has [boolean operators](https://en.wikipedia.org/wiki/Boolean_algebra) for implementing logical operations. + +Not to be confused with the [boolean geometry](csg.md) functions for working with 3D solids, ShapeScript's boolean operators work with `true` or `false` values, and are predominantly used in conjunction with `if/else` control flow statements. + +ShapeScript supports the common boolean operators: + +Operator | Function +:------------- | :-------------------- +and | Compares two values and returns `true` if they are both true +or | Compares two values and returns `true` if either one is true +not | Returns `false` if the expression to the right is true, and `true` if it's false + +
+ +Unlike some languages, ShapeScript's boolean operators are implemented as keywords rather than symbols like `&&` or `||`, so control flow statements read more like sentences: + +```swift +if a and b { + print "both a and b were true" +} +``` + +These can be combined into more complex expressions, and used in conjunction with parentheses for disambiguation: + +```swift +if (not a) and (b or c) { + print "a was false and either b or c were true" +} +``` + +## Members + +Compound values like [vectors and tuples](literals.md#vectors-and-tuples) can be decomposed by using the *dot* operator to access individual components: + +```swift +define vector 0.5 0.2 0.4 +define yComponent vector.y +print yComponent 0.2 +``` + +Like other operators, the dot operator can be used as part of a larger expression: + +```swift +color 1 0.5 0.2 +define averageColor (color.red + color.green + color.blue) / 3 +print averageColor // prints 0.5667 +``` + +For strings, you can use the `lines`, `words` and `characters` members: + +```swift +define sentence "The quick brown fox" +for word in sentence { + print word // prints each word on a new line +} +``` + +For [paths](paths.md) you can access the `bounds` and `points` members. For each point you can access the `position`, `isCurved` and `color`: + +```swift +// Print the points in a circle +for point in circle.points { + print "position: " point.position ", isCurved: " point.isCurved +} +``` + +For [meshes](meshes.md) you can access the `name`, `bounds` and `polygons` members: + +```swift +print cube.bounds.size // prints 1 1 1 +print cube.polygons.count // prints 6 +``` + +For [polygons](meshes.md#polygons-and-points) you can get the `bounds` or `center`, or use `points` to access the individual vertices. For points you can access the `position` and `color`: + +```swift +define triangle polygon { + color red + point 0 0 + color green + point 1 0 + color blue + point 1 1 +} + +// Print the vertex positions and colors +for point in triangle.points { + print "position: " point.position ", color: " point.color +} +``` + +For more information about the members that can be accessed on various data types, see [structured data](literals.md#structured-data). + + +## Ranges + +Another type of expression you can create is a *range* expression. This consists of two numeric values separated by a `to` keyword: + +```swift +1 to 5 +``` + +Ranges are mostly used in [for loops](control-flow.md#loops): + +```swift +for i in 1 to 5 { + print i +} +``` + +But they can also be assigned to a [symbol](symbols.md) using the `define` command, and then used later: + +```swift +define loops 1 to 5 + +for i in loops { + print i // prints 1, 2, 3, 4, 5 +} +``` + +**Note:** Ranges are inclusive of both the start and end values, so a loop from `0 to 5` would loop *6* times and not 5 as you might expect. + +Range values can be fractional and/or negative: + +```swift +for i in 0.2 to 2.2 { + print i // prints 0.2, 1.2, 2.2 +} + +for i in -3 to -1 { + print i // prints -3, -2, -1 +} +``` + +Ranges may also include an optional `step` value to control how the range will be enumerated: + +```swift +for i in 1 to 5 step 2 { + print i // prints 1, 3, 5 +} + +for i in 0 to 1 step 0.2 { + print i // prints 0, 0.2, 0.4, 0.6, 1 +} +``` + +The step value for an existing range can be set or overridden later: + +```swift +define loops 1 to 5 step 3 + +for i in loops { + print i // prints 1, 4 +} + +for i in loops step 2 { + print i // prints 1, 3, 5 +} +``` + +A negative `step` can be used to create a [backwards loop](control-flow.md#looping-backwards): + +```swift +for i in 5 to 1 step -1 { + print i // prints 5, 4, 3, 2, 1 +} +``` + +--- +[Index](index.md) | Next: [Functions](functions.md) diff --git a/docs/1.7.1/mac/functions.md b/docs/1.7.1/mac/functions.md new file mode 100644 index 00000000..14efaa8d --- /dev/null +++ b/docs/1.7.1/mac/functions.md @@ -0,0 +1,296 @@ +Functions +--- + +Functions consist of a name followed by one or more values. They perform an operation on their input values and return the result. + +Functions can be used inside expressions, and can accept expressions as inputs. Unlike [operators](expressions.md#operators), functions have no implicit precedence, so parentheses may be needed to avoid ambiguity. + +In the following example, it's not clear if `y` is intended as a second argument to the `cos` function, or as the second argument to the translate command. Only the latter would technically be valid, since `cos` only accepts a single argument, but ShapeScript requires you to be explicit, and will treat this as an error: + +```swift +translate cos x y +``` + +The error can be resolved by using parentheses to group the `cos` function with its argument: + +```swift +translate (cos x) y +``` + +Lisp programmers will find this syntax quite familiar, but if you have used C-like programming languages it may seem a little strange to put the parentheses around the function name and its arguments instead of just the arguments. If you prefer, you can use a C-like syntax instead: + +```swift +translate cos(x) y +``` + +Either approach is acceptable in ShapeScript. + +## Arithmetic + +In addition to the standard arithmetic [operators](expressions.md#operators), ShapeScript also includes a number of built-in arithmetic *functions*: + +The `round` function is used to round a number to the nearest integer (whole number): + +```swift +round 3.2 // returns 3 +round 3.9 // returns 4 +round 3.5 // returns 4 +``` + +The `floor` function is similar, but always rounds down: + +```swift +floor 3.2 // returns 3 +floor 3.9 // returns 3 +``` + +The `ceil` function always rounds up: + +```swift +ceil 3.2 // returns 4 +ceil 3.9 // returns 4 +``` + +The `abs` function returns the magnitude of a number, ignoring the sign: + +```swift +abs 4.5 // returns 4.5 +abs -51 // returns 51 +``` + +The `sign` function (not to be confused with [sin](#trigonometry)) returns `1`, `-1` or `0` depending on the sign of the input: + +```swift +sign 4.5 // returns 1 +sign -51 // returns -1 +sign 0 // returns 0 +``` + +The `sqrt` function returns the square root of a value: + +```swift +sqrt 4 // returns 2 +sqrt 2 // returns 1.414 +``` + +The `pow` function takes *two* parameters, and return the first value raised to the power of the second: + +```swift +pow 2 4 // returns 16 +pow 3 2 // returns 9 +pow 4 0.5 // returns 2 +``` + +The `min` function returns the lower of two or more values: + +```swift +min 2 4 // returns 2 +min 5 0 -5.1 // returns -5.1 +``` + +The `max` function returns the higher of two or more values: + +```swift +max 2 4 // returns 4 +max 5 0 -5.1 // returns 5 +``` + +## Linear Algebra + +ShapeScript also includes functions for operating on [vectors](literals.md#vectors-and-tuples): + +The `dot` function is used to calculate the [dot product](https://en.wikipedia.org/wiki/Dot_product) of a pair of vectors: + +```swift +define up (0 1) +define right (1 0) +print dot(up right) // returns 0 +``` + +The `cross` function calculates the [cross product](https://en.wikipedia.org/wiki/Cross_product): + +```swift +define up (0 1) +define right (1 0) +print cross(up right) // returns 0 0 -1 +``` + +The `length` function calculates the magnitude of a vector (also known as the [norm](https://en.wikipedia.org/wiki/Euclidean_space#Euclidean_norm)): + +```swift +define v (3 4) +print length(v) // returns 5 +``` + +The `normalize` function computes a [unit vector](https://en.wikipedia.org/wiki/Unit_vector) from an arbitrary vector by dividing each element by the vector length: + +```swift +define v (3 4) +print normalize(v) // returns 0.6 0.8 +``` + +## Trigonometry + +For the most part, you can avoid the need for trigonometry in ShapeScript by using the built-in [transform commands](transforms.md#relative-transforms) to manipulate geometry rather than manually calculating the positions of vertices. + +But sometimes you may wish to do something more complex (e.g. generating a path in the shape of a sign wave) that can only be achieved through explicit calculations, and to support that, ShapeScript provides a standard suite of trigonometric functions. + +While ShapeScript's [transform](transforms.md#orientation) commands expect values in [half-turns](https://en.wikipedia.org/wiki/Turn_(angle)), the trigonometric functions all use [radians](https://en.wikipedia.org/wiki/Radian). + +For example, the `sin` (sine) function takes a radian representation of an angle and returns a ratio value of that angle. In this case 0.524 radians returns 0.5 or 1/2 - an angle of 30 degrees: + +```swift +sin 0.524 // returns 0.5 +``` + +The `acos` (arc cosine) function takes a ratio representation of an angle and returns a radians value of that angle. In this case 1/2 or 0.5 returns 1.047 radians - equivalent to an angle of 60 degrees: + +```swift +acos 0.5 // return 1.047 +``` + +The `cos` (cosine), `sin` (sine), and `tan` (tangent) functions all take a radians value and return a ratio value, and the `asin` (arc sine), `acos` (arc cosine), and `atan` (arc tangent) functions all take a ratio value and return a radians value. + +Using `atan` to calculate the angle of a vector is problematic because the result that it returns can be ambiguous. You need to take the vector quadrant into account, as well as the ratio of the X and Y components. + +The `atan2` function works like `atan`, but instead of a single tangent value, it accepts separate Y and X inputs and returns the angle of the vector that they describe. The resultant angle correctly takes the vector quadrant into account. + +```swift +atan2 1 -1 // returns a radian angle of the vector Y: 1, X: -1 +``` + +To convert an angle in radians to a ShapeScript half-turn value, divide it by the 'pi' constant: + +```swift +define angle acos(0.5) // returns 1.047 radians (60 degrees) +rotate angle / pi // return 0.333 (1.047 / 3.141) +``` + +To convert a ShapeScript half-turn value to radians, multiply it by `pi`. + +```swift +cube { + orientation 0.5 + print orientation.roll * pi // prints 1.571 (0.5 * pi) +} +``` + +Angular conversion formulae: + +Conversion | Formula +:---------------------| :-------------------------- +Degrees to radians | radians = degrees / 180 * pi +Radians to degrees | degrees = radians / pi * 180 +Degrees to half-turns | halfturns = degrees / 180 +Half-turns to degrees | degrees = halfturns * 180 +Radians to half-turns | halfturns = degrees / pi +Half-turns to radians | radians = halfturns * pi + +
+ +Common values: + +Angle in degrees | Angle in radians | Angle in half-turns +:--------------- | :--------------- | :------------------ +0 | 0 | 0 +30 | pi / 6 (0.524) | 1 / 6 (0.167) +45 | pi / 4 (0.785) | 1 / 4 (0.25) +60 | pi / 3 (1.047) | 1 / 3 (0.333) +90 | pi / 2 (1.57) | 1 / 2 (0.5) +180 | pi (3.142) | 1 + +
+ +## Strings + +ShapeScript includes a number of functions for manipulating strings. + +The `join` function concatenates two or more strings together, with an optional separator: + +```swift +define words "Hello" "World!" +define greeting join words ", " +print greeting // prints "Hello, World!" +``` + +If you don't want a delimiter, just pass an empty string as the second argument to join: + +```swift +define words "Hello" "World!" +define greeting join(words "") +print greeting // prints "HelloWorld!" +``` + +The `split` function breaks a string up into a tuple of substrings by splitting it at a specified delimiter: + +```swift +define input "comma,delimited,string" +define elements split input "," +print elements.second // prints "delimited" +``` + +The `trim` function removes any leading or trailing [white space](https://en.wikipedia.org/wiki/Whitespace_character) characters from a string: + +```swift +define input "comma, delimited , string,with ,spaces" +define elements split input "," +print elements.second // prints " delimited " +print trim elements.second // prints just "delimited" without spaces +``` + +## Functions and Expressions + +Expressions can be passed as function arguments, for example: + +```swift +sin pi / 2 // returns 1 +``` + +Which, thanks to precedence rules, is equivalent to: + +```swift +sin(pi / 2) // returns 1 +``` + +You can also use function calls *inside* an expression, for example: + +```swift +print (sqrt 9) + (sqrt 9) // prints 6 +``` + +Or the equivalent form of: + +```swift +print sqrt(9) + sqrt(9) // also prints 6 +``` + +**Note:** When used inside an expression, parentheses around the function (or just its arguments) are required. + +## Custom Functions + +You can define your own functions using the `define` command. A function definition consists of a function name followed by a list of parameter names in parentheses: + +```swift +define sum(a b) { + a + b +} + +define degreesToRadians(degrees) { + degrees / 180 * pi +} +``` + +Like [blocks](blocks.md), functions can refer to constant values or other functions defined in their containing [scope](scope.md): + +```swift +define epsilon 0.0001 + +define almostEqual(a b) { + abs(a - b) < epsilon +} +``` + +Unlike [block options](blocks.md#options), function inputs do not have default values. Calling a function without passing a value for every input will result in an error. + +--- +[Index](index.md) | Next: [Commands](commands.md) diff --git a/docs/1.7.1/mac/getting-started.md b/docs/1.7.1/mac/getting-started.md new file mode 100644 index 00000000..28694204 --- /dev/null +++ b/docs/1.7.1/mac/getting-started.md @@ -0,0 +1,114 @@ +Getting Started +--- + +Start by creating a new ShapeScript document. You can do this by selecting `File > New` in the menu bar (or by pressing **Cmd-N** on the keyboard). You will be prompted to select a name and location for your new file - you can call it anything you want, but be sure to retain the `.shape` file extension. + +**Note:** If you cannot see the file prompt, check that it hasn't appeared *behind* this help window. The help window floats in front of all other windows and applications, so you may need to move it out of the way. + +You should see a new window appear containing a cube, sphere and cone. These are the default contents for a new file (don't worry, you can replace them with your own shapes). + +![New document window](../../images/new-document-mac.png) + +## Viewing + +Drag your mouse cursor inside the window and you will notice that the three objects appear to rotate. + +You are not actually moving the shapes themselves, but rotating the camera around them. To reset the view, select `View > Camera > Reset` or press **Cmd-0**. + +To help you orient yourself, you can use the `View > Show Axes` option, which displays the direction of the X, Y and Z axes. + +![Show axes](../../images/show-axes-mac.png) + +For more information about navigating around the model, see [Camera Control](camera-control.md). + +## Editing + +To edit the 3D model, select the `Edit > Open in Editor` menu or press **Cmd-E**. + +ShapeScript does not include an editor. Instead, the `Open in Editor` command will ask you to select an external text editor to use. If you do not have any other editors installed, the default option will probably be TextEdit. Once opened, the file should look something like this: + +![TextEdit window](../../images/textedit-mac.png) + +ShapeScript files are just ordinary text documents, so they can be opened in any program that supports editing text. TextEdit will work, but is probably not the best choice as it does not support syntax coloring or any of the other programmer-friendly features of modern editors. + +If you have a more advanced editor such as [SubEthaEdit](https://subethaedit.net), [TextMate](https://macromates.com), [Sublime Text](https://www.sublimetext.com) or [Atom](https://atom.io) then it would be better to use one of these. An Integrated Development Environment (IDE) such as [Xcode](https://developer.apple.com/xcode/) is also a good option. + +No third-party editors currently support ShapeScript syntax directly, but if you set your editor to treat `.shape` files as another C-like language (e.g. C, Swift or Java) then it should do an acceptable job of syntax coloring and indenting. Some editors (such as Xcode) will make this association automatically, which will save you having to do it each time you edit a ShapeScript file. + +ShapeScript will only ask you which editor you want to use the *first* time you edit a file, then it will remember your choice. If you change your mind later, you can select a different editor in Preferences (select the `ShapeScript > Preferences…` menu or press **Cmd-,**). + +## File Structure + +The first line of the default `.shape` document looks like this: + +```swift +// ShapeScript document +``` + +Lines beginning with `//` are comments, and are ignored by the parser. You can use these comments to document what the different parts of your file are doing. + +The next line is: + +```swift +detail 32 +``` + +This controls the level of detail used for shapes in the file. ShapeScript models are made out of flat polygons, so it is not possible for curved edges to be represented exactly. When you define a sphere or other curved shape, the `detail` value is used to decide how many polygons will be used. + +Next we have the following: + +```swift +cube { + position -1.5 + color 1 0 0 +} +``` + +These three lines (ignoring the closing `}`) represent three distinct instructions, although they are grouped together because they all relate to a single shape. White space (such as spaces, tabs, or blank lines) is mostly ignored, however each new instruction must be placed on a new line. + +The first instruction, `cube`, creates the cube shape. Note the `{ ... }` braces after `cube`. Content inside braces relates to the instruction that precedes them. In this case, the braces contain instructions that set the `position` and `color` of the cube. + +The line `position -1.5` means "position the shape 1.5 spacial units to the left of the origin". The *origin* is the center of the world, and is the default position at which new shapes will appear. + +**Note:** The spacial units ShapeScript uses are arbitrary, and can represent anything that you wish them to: a centimeter, a meter, an inch, a mile, etc. Try to choose an appropriate scale for your models so that you avoid having to work with very large scales (thousands of units), or very small scales (thousandths of a unit). That will help to avoid rendering precision issues, as well as making your scripts easier to work with. + +The `position` keyword is followed by up to 3 numbers representing offsets along the X, Y, and Z axes respectively. If values are omitted, they are assumed to be zero, so a position o-f `-1.5` is equivalent to `-1.5 0 0`. This is covered in detail in the [transforms](transforms.md#position) section. + +The next line, `color 1 0 0` sets the current color. The three numbers after the keyword represent the red, green, and blue color channels, each with a range from 0 to 1. Because the red channel is 1 and the other channels are 0, the resultant color will be pure red. + +As with `position`, you can omit values for the `color`, but the behavior is slightly different: If you only specify one value then that will be used for all three color channels. You can optionally specify a fourth "alpha" value to control transparency. This is covered in detail in the [materials](materials.md#color) section. + +The groups of instructions after the cube are similar, but define a sphere and cone respectively. + +## Next Steps + +Change the `color 1 0 0` instruction to `color 1 1 0`, and then save the file. If you switch back to the ShapeScript app you should now see that the red cube has changed to yellow. ShapeScript tracks changes to any open files and will automatically update the view whenever they are saved. + +![Yellow cube](../../images/yellow-cube-mac.png) + +Try tweaking the values for the `position` and `color` instructions and observing the effects. You could also try adding a `size` option, which works like the others, but controls the *size* of the shape. + +If you make a mistake, you will see a screen like this: + +![Error screen](../../images/error-screen-mac.png) + +There is no cause for alarm if you see this screen. Just fix your mistake and save again to clear the error. If you aren't sure what you did, and the error message doesn't help, use the `Edit > Undo` menu in your editor to backtrack to a working state and try again. + +When you are comfortable with manipulating the default shapes, try deleting them and adding new ones of your own. + +**Note:** Many text editors save automatically every few seconds, so you may find that ShapeScript applies your changes as you type, even if you haven't explicitly saved. If you are in the middle of typing a new command when the automatic refresh takes place, ShapeScript may display an error, just as if you had manually saved an incomplete file. Errors that appear while you are still typing can be safely ignored. + +## Debugging and Selection + +You can get information about your scene (such as polygon count and overall dimensions) by selecting `View > Scene Info` (**Cmd-I**) in the menu bar. + +To get info about a particular shape in the scene, click on it in the viewer to select it, then use `View > Model Info` (**Cmd-I**) to get info on just the selected part. This will also tell you which line in the `.shape` file defines that component. Click outside of the shape, or press **Escape** to deselect it again. + +![Object info](../../images/object-info-mac.png) + +You can also use the `Edit > Select Shape` menu to select from a list of shapes in the scene, or cycle between selected shapes using the **Tab** key. + +**Note:** If you are having trouble identifying a particular shape from your `.shape` file, you can use the [debug command](debugging.md) to highlight it in the viewer. + +--- +[Index](index.md) | Next: [Camera Control](camera-control.md) diff --git a/docs/1.7.1/mac/glossary.md b/docs/1.7.1/mac/glossary.md new file mode 100644 index 00000000..36bd57f7 --- /dev/null +++ b/docs/1.7.1/mac/glossary.md @@ -0,0 +1,29 @@ +Glossary +--- + +Term | Definition +:--------------------| :-------------------------- +Block | A group of commands inside braces (sometimes synonymous with Scope) +Builder | A command for creating a 3D mesh out of one or more 2D paths +Color | An RGBA color to be applied to a mesh or vertex +CSG | Constructive Solid Geometry - adding or subtracting meshes to make new shapes +Function | A named, parameterized block that returns a value +Geometry | A collective term for 2D or 3D shapes produced using ShapeScript +Group | A group of paths or meshes. May also contain nested groups +Half-turn | The unit used to specify angles or rotations in ShapeScript +Material | A color or texture +Mesh | A 3D shape made of polygons +Member | A single component of a compound value like a vector or tuple +Path | A line or curve in 3D space. May be open-ended or closed +Primitive | A simple, built-in shape like a cube or sphere +Range | A numeric range with a start, end and step value +Scope | A section of code with its own symbol namespace +Symbol | A named constant value or function +Texture | An image map to be wrapped around a mesh +Transform | A combined translation, rotation and scale +Translation | A relative or absolute position or offset in 3D space +Tuple | A list of values that can be accessed with .first, .second, etc. +Vector | A position, direction or distance in 3D space + +--- +[Index](index.md) \ No newline at end of file diff --git a/docs/1.7.1/mac/groups.md b/docs/1.7.1/mac/groups.md new file mode 100644 index 00000000..20ef9505 --- /dev/null +++ b/docs/1.7.1/mac/groups.md @@ -0,0 +1,42 @@ +Groups +--- + +Complex 3D shapes often follow a natural hierarchy - a car has wheels, or a torso has arms and legs, etc. You can create a hierarchy in ShapeScript by using the `group` command: + +```swift +group { + position -0.5 0 0 + sphere { + position 0 0 0 + } + cylinder { + position 1 0 0 + } +} +``` + +Like other 3D shapes, a group has a `position` and `orientation` in 3D space, but it can also contain child shapes which inherit its coordinate system; if you move the group, its children move with it. The `position`, `orientation` and `size` properties of the children will be treated as relative to those of the containing group. + +You can override the current [options](options.md) (material, detail, etc.) at any point inside a group and only the subsequent children will be affected. You can also use [relative transform](transforms.md#relative-transforms) commands to manipulate the position of subsequent shapes inside the group: + +```swift +group { + color 1 0 0 + sphere + translate 1 + color 0 1 0 + cylinder + translate 1 + rotate 0.1 + scale 0.5 + color 0 0 1 + cube +} +``` + +![Group](../../images/group.png) + +**Note:** Changes made to properties such as transform or material are [scoped](scope.md) to the group, and will not carry beyond the closing `}`. + +--- +[Index](index.md) | Next: [Lights](lights.md) diff --git a/docs/1.7.1/mac/import.md b/docs/1.7.1/mac/import.md new file mode 100644 index 00000000..cbaf594a --- /dev/null +++ b/docs/1.7.1/mac/import.md @@ -0,0 +1,78 @@ +Import +--- + +## Scripts + +ShapeScript files can get quite large for complicated scenes, and you may find that there are common shapes that you wish to across multiple ShapeScript files. The `import` command can help with both of these problems: + +```swift +import "MyShape.shape" +``` + +Here we've used `import` to load an external ShapeScript file and evaluate it inside the calling script. Any symbols that are defined in the imported file will become available inside the [scope](scope.md) in which it is loaded, and any geometry created by the imported file will be displayed. + +You can import the same file several times in the same script, and import statements can appear inside [loops](control-flow.md#loops), [if statements](control-flow.md#if-else) or [blocks](blocks.md). If you don't want the geometry inside an imported file to be displayed immediately, you can place the import inside a [define](symbols.md) statement: + +```swift +define ball { + import "Ball.shape" +} +``` + +In this way, the loaded shape is bound to a symbol of your choice, and can be used a later point in the script. This approach also prevents any [symbols](symbols.md) defined in the imported script from leaking outside into the calling script's global [scope](scope.md). + +**Note:** As with textures, the first time you try to import a file you may see an [access permission](materials.md#access-permission) warning. + +## Models + +The `import` command is not limited to loading `.shape` files. It can load models in many of the [export formats](export.md) supported by ShapeScript. Imported models can be used just like imported script files: + +```swift +define rocket { + texture "Rocket.png" + import "Rocket.obj" +} +``` + +Depending on the format, imported models may include their own [materials](materials.md). Uncolored / untextured models will inherit the current ShapeScript material properties. + +## Text and Data + +In addition to scripts and standard model formats, text files can be imported as a [string](literals.md#strings) value. + +```swift +define text { + import "Text.txt" +} +``` + +Imported strings can then be displayed directly, or further processed using ShapeScript's [string functions](functions.md#strings): + +```swift +for line in text.lines { + print (trim line) +} +``` + +As well as plain text, ShapeScript also supports importing JSON files as [structured data](literals.md#structured-data): + +```swift +define data { + import "Data.json" +} +``` + +## Dynamic Imports + +It can sometimes be useful to generate the name of an imported file dynamically. For example if you have multiple numbered files to import, you might want to generate the names programatically. + +You can do this using the [text interpolation](text.md#interpolation) feature. The following code, for example, will load the files "Shape1.shape", "Shape2.shape"... up to "Shape10.shape": + +```swift +for n in 1 to 10 { + import "Shape" n ".shape" +} +``` + +--- +[Index](index.md) | Next: [Export](export.md) diff --git a/docs/1.7.1/mac/index.md b/docs/1.7.1/mac/index.md new file mode 100644 index 00000000..af47eada --- /dev/null +++ b/docs/1.7.1/mac/index.md @@ -0,0 +1,165 @@ +ShapeScript Help +--- + +- [Getting Started](getting-started.md) + - [Viewing](getting-started.md#viewing) + - [Editing](getting-started.md#editing) + - [File Structure](getting-started.md#file-structure) + - [Next Steps](getting-started.md#next-steps) + - [Debugging and Selection](getting-started.md#debugging-and-selection) +- [Camera Control](camera-control.md) + - [Camera Selection](camera-control.md#camera-selection) + - [Mouse Control](camera-control.md#mouse-control) + - [Trackpad Control](camera-control.md#trackpad-control) + - [Copy Settings](camera-control.md#copy-settings) +- Geometry + - [Primitives](primitives.md) + - [Cube](primitives.md#cube) + - [Sphere](primitives.md#sphere) + - [Cylinder](primitives.md#cylinder) + - [Cone](primitives.md#cone) + - [Options](options.md) + - [Name](options.md#name) + - [Detail](options.md#detail) + - [Smoothing](options.md#smoothing) + - [Transform](options.md#transform) + - [Material](options.md#material) + - [Materials](materials.md) + - [Color](materials.md#color) + - [Texture](materials.md#texture) + - [Opacity](materials.md#opacity) + - [Transforms](transforms.md) + - [Position](transforms.md#position) + - [Orientation](transforms.md#orientation) + - [Size](transforms.md#size) + - [Relative Transforms](transforms.md#relative-transforms) + - [Bounds](bounds.md) + - [Mesh Bounds](bounds.md#mesh-bounds) + - [Path Bounds](bounds.md#path-bounds) + - [Bounds Members](bounds.md#bounds-members) + - [Meshes](meshes.md) + - [Polygons and Points](meshes.md#polygons-and-points) + - [Watertightness](meshes.md#watertightness) + - [Winding order](meshes.md#winding-order) + - [Procedural Meshes](meshes.md#procedural-meshes) + - [Concave and Non-planar Polygons](meshes.md#concave-and-non-planar-polygons) + - [Paths](paths.md) + - [Points](paths.md#points) + - [Curves](paths.md#curves) + - [Arcs](paths.md#arcs) + - [Circles](paths.md#circles) + - [Rectangles](paths.md#rectangles) + - [Regular Polygons](paths.md#regular-polygons) + - [Procedural Paths](paths.md#procedural-paths) + - [Nested Paths](paths.md#nested-paths) + - [Path Colors](paths.md#path-colors) + - [SVG Paths](paths.md#svg-paths) + - [Text](text.md) + - [Interpolation](text.md#interpolation) + - [Wrap Width](text.md#wrap-width) + - [Size and Line Height](text.md#size-and-line-height) + - [Line Spacing](text.md#line-spacing) + - [Position and Orientation](text.md#position-and-orientation) + - [Font](text.md#font) + - [Builders](builders.md) + - [Fill](builders.md#fill) + - [Lathe](builders.md#lathe) + - [Extrude](builders.md#extrude) + - [Loft](builders.md#loft) + - [Hull](builders.md#hull) + - [Constructive Solid Geometry](csg.md) + - [Difference](csg.md#difference) + - [Intersection](csg.md#intersection) + - [Union](csg.md#union) + - [XOR](csg.md#xor) + - [Stencil](csg.md#stencil) + - [Groups](groups.md) + - [Lights](lights.md) + - [Ambient](lights.md#ambient) + - [Directional](lights.md#directional) + - [Point Lights](lights.md#point-lights) + - [Spotlights](lights.md#spotlights) + - [Debugging](lights.md#debugging) + - [Cameras](cameras.md) + - [Custom Cameras](cameras.md#custom-cameras) + - [Position](cameras.md#position) + - [Orientation](cameras.md#orientation) + - [Field of View](cameras.md#field-of-view) + - [Orthographic View](cameras.md#orthographic-view) + - [Background](cameras.md#background) + - [Pixel Dimensions](cameras.md#pixel-dimensions) +- Syntax + - [Comments](comments.md) + - [Block Comments](comments.md#block-comments) + - [Nested Comments](comments.md#nested-comments) + - [Literals](literals.md) + - [Strings](literals.md#strings) + - [Vectors and Tuples](literals.md#vectors-and-tuples) + - [Structured Data](literals.md#structured-data) + - [Symbols](symbols.md) + - [Expressions](expressions.md) + - [Operators](expressions.md#operators) + - [Equality and Comparison](expressions.md#equality-and-comparison) + - [Linear Algebra](expressions.md#linear-algebra) + - [Boolean Algebra](expressions.md#boolean-algebra) + - [Members](expressions.md#members) + - [Ranges](expressions.md#ranges) + - [Functions](functions.md) + - [Arithmetic](functions.md#arithmetic) + - [Linear Algebra](functions.md#linear-algebra) + - [Trigonometry](functions.md#trigonometry) + - [Strings](functions.md#strings) + - [Functions and Expressions](functions.md#functions-and-expressions) + - [Custom Functions](functions.md#custom-functions) + - [Commands](commands.md) + - [Background](commands.md#background) + - [Detail](commands.md#detail) + - [Smoothing](commands.md#smoothing) + - [Materials](commands.md#materials) + - [Font](commands.md#font) + - [Transforms](commands.md#transforms) + - [Primitives](commands.md#primitives) + - [Paths](commands.md#paths) + - [Text](commands.md#text) + - [Builders](commands.md#builders) + - [Constructive Solid Geometry](commands.md#constructive-solid-geometry) + - [Random Numbers](commands.md#random-numbers) + - [Debugging](commands.md#debugging) + - [Import](commands.md#import) + - [Control Flow](control-flow.md) + - [Loops](control-flow.md#loops) + - [Loop Index](control-flow.md#loop-index) + - [Looping Backwards](control-flow.md#looping-backwards) + - [Looping Over Values](control-flow.md#looping-over-values) + - [If-Else](control-flow.md#if-else) + - [Conditional Defines](control-flow.md#conditional-defines) + - [Blocks](blocks.md) + - [Options](blocks.md#options) + - [Scope](scope.md) + - [Block Scope](scope.md#block-scope) + - [Function Scope](scope.md#function-scope) + - [Conditional Scope](scope.md#conditional-scope) + - [Debugging](debugging.md) + - [Logging](debugging.md#logging) + - [Assertions](debugging.md#assertions) + - [Import](import.md) + - [Scripts](import.md#scripts) + - [Models](import.md#models) + - [Text and Data](import.md#text-and-data) + - [Dynamic Imports](import.md#dynamic-imports) +- [Export](export.md) + - [Export Formats](export.md#export-formats) + - [Games and AR](export.md#games-and-ar) + - [3D Printing](export.md#3d-printing) + - [Image Formats](export.md#image-formats) + - [Image Options](export.md#image-options) +- [Examples](examples.md) + - [Ball](examples.md#ball) + - [Chessboard](examples.md#chessboard) + - [Cog](examples.md#cog) + - [Earth](examples.md#earth) + - [Icosahedron](examples.md#icosahedron) + - [Spirals](examples.md#spirals) + - [Spring](examples.md#spring) + - [Train](examples.md#train) +- [Glossary](glossary.md) diff --git a/docs/1.7.1/mac/lights.md b/docs/1.7.1/mac/lights.md new file mode 100644 index 00000000..e9808e76 --- /dev/null +++ b/docs/1.7.1/mac/lights.md @@ -0,0 +1,177 @@ +Lights +--- + +By default, scenes created in ShapeScript are lit with a bright, white, camera-aligned directional light. This is good for viewing purposes, but limited. + +ShapeScript allows you to add your own lights to the scene, which can help produce a nicer effect when rendering a scene for display. + +Lights come in four basic types, all created using the `light` command. + +**Note:** there is a maximum of 8 non-ambient lights per scene. If you need more, consider pre-lighting your models using [colors](materials.md#color) or [textures](materials.md#texture). + +## Ambient + +An ambient light is one that illuminates all objects in the scene uniformly. It does not have a position or direction, and does not cast shadows. + +To define an ambient light, add the following to your scene: + +```swift +light {} +``` + +This creates a white ambient light at full brightness. The ambient light is applied in addition to the default lighting, with the result that the scene appears over-bright, or *washed out*: + +![Over-bright ambient light](../../images/washed-out.png) + +To solve this, you can reduce the intensity of the light by setting its color: + +```swift +light { + color 0.5 // 50% intensity +} +``` + +The `color` parameter of the light behaves just like any other [color](materials.md#color) in ShapeScript. You can pass up to four values representing the red, green blue and alpha components. + +```swift +light { + color 1 0 1 // purple light +} + +light { + color #FF00FF // purple again, this time specified as a hex color literal +} +``` + +Alpha transparency isn't really meaningful in the context of a light source, so you can think of the fourth (alpha) component of the color as its *intensity*. In the first example above we uses a color value of 0.5 to indicate 50% intensity, but an alternative way to specify this would be to use a color with 50% alpha: + +```swift +light { + color 1 0.5 // white light with 50% intensity +} + +light { + color #FF7F00 0.25 // orange light with 25% intensity +} +``` + +The advantage of this is that you can use the alpha component to vary the intensity of an arbitrary color, rather that having to calculate each component individually. + +## Directional + +Directional lights simulate a distant light source, like the Sun. To create a directional light, add an `orientation` component to your light: + +```swift +light { + color white + orientation 0 0 0 +} +``` + +An orientation of `0 0 0` (or just `0` for short) creates a light that points directly down the Z axis, and is equivalent to the default light for a front-facing camera (unlike the default lighting, this light won't move with the camera if you rotate the view however). + +The three values represent the rotation around the Z, Y and X axes respectively. To create a light that shines from the left you could use: + +```swift +light { + orientation 0 0.5 0 +} +``` + +This rotates the light by 90 degrees (`0.5 * 180`) around the vertical (Y) axis. + +![Directional light shining from the left](../../images/left-light.png) + +**Note:** unlike ambient light, adding a directional light to the scene disables/replaces the default lighting. + +Lights are also affected by [relative transforms](transforms.md#relative-transforms), so the following would be equivalent to the above: + +```swift +rotate 0 0.5 0 +camera { + orientation 0 +} +``` + +**Note:** the `orientation 0` is still required, otherwise the light will be treated as ambient and will not be affected by the rotation. + +## Point Lights + +A point light is an omnidirectional light that exists at a particular location in space. It has a position, but no orientation: + +```swift +camera { + position -5 0 0 +} +``` + +As you can see, the effect of placing a point light to the left of your scene is similar to a direction light facing from this direction. Unlike a directional light however, a point light can be placed in between objects in the scene: + +```swift +camera { + position 0.5 0 0 +} +``` + +![Point light between objects](../../images/point-light.png) + +## Spotlights + +A spotlight has both a position *and* direction. The following code creates a yellow spotlight located just to the left of the enter of the scene, pointing down and to the right: + +```swift +light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 +} +``` + +![Spotlight](../../images/spotlight.png) + +By default, spotlights cast light in a 45-degree cone. You can alter this with the `spread` option: + +```swift +light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 + spread 0.5 +} +``` + +The spread is a value between 0 and 1 representing a cone angle from 0 to 180 degrees, so `0.5` in the example maps to a 90-degree cone: + +![Wide spotlight](../../images/spotlight-wide.png) + +You've probably noticed that the edge of the light cast by the spotlight is blurred. The blurred region around the spotlight is known as the [penumbra](https://en.wikipedia.org/wiki/Umbra,_penumbra_and_antumbra#Penumbra), and can be adjusted using the `penumbra` option: + +```swift +light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 + penumbra 0 +} +``` + +The `penumbra` is specified as a value between 0 and 1 representing the proportion of the total spotlight cone that should be blurred. The default is `1` (meaning the blur is spread right across the cone). A value of zero results in a completely sharp edge: + +![Sharp spotlight](../../images/spotlight-sharp.png) + +## Debugging + +Since it can be hard to visualize the position of a spot or point light, you can use the [debug command](debugging.md) to make the light temporarily visible in the ShapeScript viewer: + +```swift +debug light { + color yellow + position -0.75 0 0 + orientation -0.15 0 0.5 +} +```swift + +![Debugging a spotlight](../../images/spotlight-debug.png) + +--- +[Index](index.md) | Next: [Cameras](cameras.md) diff --git a/docs/1.7.1/mac/literals.md b/docs/1.7.1/mac/literals.md new file mode 100644 index 00000000..6334cd03 --- /dev/null +++ b/docs/1.7.1/mac/literals.md @@ -0,0 +1,200 @@ +Literals +--- + +Literals are values that you type in your ShapeScript file. They could be numbers, text or something else. Literals can be passed as parameters to [options](options.md), [functions](functions.md) or [commands](commands.md), or used in [expressions](expressions.md). Here are some examples: + +```swift +5 // an integer literal + +37.2 // a decimal literal + +false // a boolean literal + +"hello" // a string literal + +1 0 0 // a vector literal + +#FF0000 // a hex color literal +``` + +## Strings + +Strings are how human-readable text is represented in ShapeScript. These are used for a variety of purposes, including specifying file names for [imports](import.md) or [textures](materials.md#texture), [logging](debugging.md#logging) debug information to the console, or even rendering 3D text. + +String literals are delimited by double quotes (`"`) to prevent ambiguity if the text matches a keyword or [symbol](symbols.md), or contains spaces or other punctuation. If you want to use a double quote *inside* the text itself then it must be *escaped* using a backslash (`\`) character: + +```swift +"hello \"Bob\" (if that's your real name)" +``` + +A line containing a string literal without a closing `"` is treated as an error, so if you need to include line-breaks in your string then these must also be escaped. Use the escape sequence `\n` (short for "new line") to indicate a line-break: + +```swift +"first line\nsecond line" +``` + +Because `\` is used as the escape character, it must also be escaped if you want it to appear literally in the string. Use a double `\\` in this case (there is no need to escape forward slashes (`/`) however): + +```swift +"back slash: \\" +"forward slash: /" +``` + +## Vectors and Tuples + +A sequence of values separated by spaces defines a [tuple](https://en.wikipedia.org/wiki/Tuple). A tuple of numeric values is also known as a *vector*. + +Vectors are used in ShapeScript to represent positions, colors, sizes and rotations. Many [commands](https://github.com/nicklockwood/ShapeScript/blob/develop/Help/commands.md) in ShapeScript accept a vector argument, and you can pass a tuple to these commands directly: + +```swift +translate 1 0 0 +``` + +You can also [define](symbols.md) a tuple value to use later: + +```swift +define size 1 1 0.5 +``` + +A tuple defined in this way doesn't know if it's going to be used as a vector - it's just a sequence of numbers. We might guess from the name "size" that it will be used to set the size of something, but there's nothing preventing you from using it as, say, a [color](materials.md#color) value: + +```swift +define size 1 1 0.5 +color size // sets color to yellow +``` + +Tuples are't limited to numbers. They can be comprised of any type of value (including other tuples), or a mix of different types: + +```swift +define size 1 2 3 +define myTuple2 "hello" 5.3 size +``` + +## Structured Data + +As well as simple values like numbers and text, it can sometimes be useful to group together sets of related data. Tuples can be nested arbitrarily to create complex data structures: + +```swift +define matrix (1 2 3) (4 5 6) (7 8 9) +``` + +Parentheses are used here to indicate that this is a tuple of three nested tuples, and not a single tuple of nine numbers. To make this more readable, you can wrap the data over multiple lines inside outer parentheses, and even use [comments](comments.md) if you wish: + +```swift +define matrix ( + (1 2 3) // position + (4 5 6) // scale + (7 8 9) // orientation +) +``` + +**Note:** The line breaks have no semantic meaning here, they only serve to make the code more readable. Only the parentheses are used to determine the grouping. + +Since no built-in commands in ShapeScript consume structured data like this, you need a way to access individual elements. You can do this in two ways: + +To extract individual values from a tuple, you can use [member syntax](expressions.md#members). If the tuple is numeric and shaped like a vector, size, rotation or color then you can use the `x`/`y`/`z`, `width`/`height`/`depth`, `roll`/`yaw`/`pitch` or `red`/`green`/`blue`/`alpha` members respectively: + +```swift +define pos 1 2 +print pos.x // 1 +print pos.y // 2 +print pos.z // 0 + +define size 3 4 5 +print size.width // 3 +print size.height // 4 +print size.depth // 5 + +define rotation 0.5 0.25 0 +print rotation.roll // 0.5 +print rotation.yaw // 0.25 +print rotation.pitch // 0 + +define col 1 0 0 +print col.red // 1 +print col.green // 0 +print col.blue // 0 +print col.alpha // 1 +``` + +For more abstract data, you can use the ordinal members (`first`, `second`, `third`, ... `last`) to access members by index: + +```swift +define data ( + "cube" // name + (1 2 3) // position + #ff0000 // color +) + +print data.count // 3 +print data.first // name +print data.second // position +print data.last // color +``` + +Member expressions can be chained, so something like this will also work: + +```swift +print data.second.x // x component of the position +``` + +You can split up lists of data using the `allButFirst` and `allButLast` members: + +```swift +define data (1 2 3 4 5) + +print data.allButFirst // 2 3 4 5 +print data.allButLast // 1 2 3 4 +``` + +For list-like data, you can use a [for loop](control-flow.md#looping-over-values) to loop over the top-level values: + +```swift +define positions ( + (1 1 2) + (2 1 2) + (3 1 2) +) + +for p in positions { + cube { + position p + } +} +``` + +You can even use nested loops to access sub-elements: + +```swift +define matrix ( + (1 2 3) + (4 5 6) + (7 8 9) +) + +for row in matrix { + for column in row { + print column // prints 1 to 9 + } +} +``` + +**Note:** There is no requirement that rows in such a structure are the same type or length: + +```swift +define data ( + (1 2 3) + (10) // single element + () // empty tuple + ("hello" "world") // non-numeric +) + +for row in data { + for element in row { + print element // prints 1, 2, 3, 10, "hello", "world" + } +} +``` + +--- +[Index](index.md) | Next: [Symbols](symbols.md) diff --git a/docs/1.7.1/mac/materials.md b/docs/1.7.1/mac/materials.md new file mode 100644 index 00000000..67d80887 --- /dev/null +++ b/docs/1.7.1/mac/materials.md @@ -0,0 +1,239 @@ +Materials +--- + +By default, all geometry that you create in ShapeScript appears as if it were made of a matte white plastic. You can alter this appearance using *materials*. + +Material support in ShapeScript is fairly limited at the moment, but you can set the *color* and *texture* of shapes. + +A given shape can only have either a color or texture, but not both. Setting the texture will clear the color and vice-versa. + +## Color + +You can alter the color of your shapes using the `color` (or `colour`) command, which accepts a color value in a variety of formats: Numeric RGB, hexadecimal or predefined: + +The following commands all produce a red cube: + +```swift +cube { color 1 0 0 } +cube { color #FF0000 } +cube { color #F00 } +cube { color red } +``` + +Because `color` is a command rather than an option, you can use it anywhere in your program and it will affect all subsequent shapes that you create. If you use the `color` command *inside* a shape or [group](groups.md) then its effect will end at the closing `}`: + +```swift +color green +group { + color red + cube // red cube +} +sphere // green sphere +``` + +### RGB Colors + +As mentioned in [Getting Started](getting-started.md), `color` can accept a tuple of up to 4 values that represent the red, green, blue and alpha color channels respectively. Values should be specified in the range 0 (no color) to 1 (full color): + +```swift +color 1 0 0 // bright red +color 0.5 0 0 // a darker shade of red +color 0.5 0.5 0 // an olive green +``` + +The red, green and blue channels control the color itself, and the alpha channel controls transparency. An alpha of 0 is fully transparent (invisible), and an alpha of 1 is fully opaque. If omitted, the alpha value defaults to 1. Alpha values between 0 and 1 can be used to create *translucent* colors, which blend with the background behind them. The following code would produce a 50% transparent red sphere: + +```swift +sphere { color 1 0 0 0.5 } +``` + +If fewer than 3 parameters are passed to the `color` command, the first parameter is treated as the overall luminance (brightness) value, meaning that the resultant color will be set to a shade of gray between 0 (black) and 1 (white). + +The table below shows how RGBA color values are interpreted, based on the number of components: + +Number of parameters | Meaning +:--------------------------- | :-------------------------- +1 | Luminance +2 | Luminance and alpha +3 | Red, green and blue +4 | Red, green, blue and alpha + +
+ +For example, the following both produce a light shade gray of gray, with a luminance of 0.8: + +```swift +color 0.8 +color 0.8 0.8 0.8 +``` + +And the following both produce 50% a translucent white, with full luminance and 50% alpha : + +```swift +color 1 0.5 +color 1 1 1 0.5 +``` + +### Hexadecimal Colors + +Instead of numeric values, you can use web-style hex codes to specify colors. These consist of a hash character (`#`) followed by 3 or 4 pairs of hexadecimal digits to specify color components in the range 0-255. Hex color codes are a popular convention and are supported by many graphics tools. Here are some examples: + +```swift +color #FF0000 // pure red +color #7F7F7F // 50% gray +color #000000 // pure black +``` + +As with numeric RGB colors, hex colors are opaque by default, but you can vary the alpha by adding a fourth pair of hexadecimal digits in the range `00` (fully transparent) to `FF` (fully opaque)`. A value of `7F` results in 50% transparency: + +```swift +color #FF00007F // 50% transparent red +``` + +As with [web colors](https://en.wikipedia.org/wiki/Web_colors), you can use a three-digit "short hex" format as follows: + +```swift +color #F00 // equivalent to #FF0000 +``` + +And you can also use a fourth digit to specify alpha: + +```swift +color #F006 // equivalent to #FF000066 +``` + +### Predefined Colors + +ShapeScript defines some built-in color constants for you to use: + +Name | R G B | Hexadecimal | Short Hex +:---------| :-----------| :-----------| :--------- +black | 0 0 0 | #000000 | #000 +blue | 0 0 1 | #0000FF | #00F +green | 0 1 0 | #00FF00 | #0F0 +cyan | 0 1 1 | #00FFFF | #0FF +red | 1 0 0 | #FF0000 | #F00 +magenta | 1 0 1 | #FF00FF | #F0F +yellow | 1 1 0 | #FFFF00 | #FF0 +white | 1 1 1 | #FFFFFF | #FFF +orange | 1 0.5 0 | #FF7F00 | - +gray/grey | 0.5 0.5 0.5 | #7F7F7F | - + +
+ +You can override these built-in colors using the `define` command, or define new colors of your own: + +```swift +define red 1 0.3 0.1 // override the default red color with a custom shade + +define lightGray 0.8 // define a new color "lightGray" with 80% luminance +``` + +### Alpha Override + +The predefined colors are all opaque (have an alpha of 1) by default, but you can override the alpha as follows: + +```swift +color red 0.5 // set color to red with 50% alpha + +define greenGlass green 0.2 +color greenGlass // set color to green with 20% alpha +``` + +This approach also works for user-defined color constants, and with RGB and hexadecimal color literals: + +```swift +define skyBlue 0.5 0.8 1 // opaque blue +color skyBlue 0.5 // 50% transparent blue + +color #ff0 0.5 // 50% transparent yellow +``` + +## Texture + +A texture is an image that is wrapped around a 3D shape, either as decoration, or to give the appearance of more surface detail than is actually there. + +You can set the texture for your shapes using the `texture` command: + +```swift +sphere { + texture "filename.png" +} +``` + +The parameter for the `texture` command is the name of an external image file to display. The name can include either an absolute or relative file path, enclosed in double quotes. If a relative path is used it should be specified relative to the ShapeScript file that references it. + +The texture name can be constructed dynamically by using [string interpolation](text.md#interpolation), which is useful if you have multiple texture files with a common prefix or suffix: + +```swift +for n in 1 to 5 { + cube { + texture "file" n ".png" + position n + } +} +``` + +As with `color`, once a texture has been set it will be applied to all subsequent shapes defined in the same [scope](scope.md). To clear the current texture you can set it to an empty string: + +```swift +texture "" +``` + +### Access Permission + +The first time you try to use an image, you will see an error screen like the one below. + +![Sandbox error](../../images/sandbox-error.png) + +This is because Apple employs a security feature called [sandboxing](https://en.wikipedia.org/wiki/Sandbox_(computer_security)) to prevent apps from accessing files without the user's permission. Use the `Grant Access` button to open the containing folder for your images. If you prefer, you can just grant access to the specific image file, but in that case you will need to grant access individually to each new texture that you use. + +### Texture Wrapping + +How a texture is applied depends on the shape. Different shape types have different default wrapping schemes: + +![Textured cube](../../images/textured-cube.png) + +![Textured sphere](../../images/textured-sphere.png) + +![Textured cylinder](../../images/textured-cylinder.png) + +![Textured cone](../../images/textured-cone.png) + +Currently there is no way to override the default wrapping scheme, but the way that you create a shape affects the way that it is textured. + +For example, creating a cube using the `cube` command will result in a different texture wrapping effect than creating it by [extruding](builders.md#extrude) a square. + +## Opacity + +Opacity is a measure of how transparent an object is. You can vary the opacity for an object or group using the alpha property of the `color` (as described [above](#color)), but sometimes you may want to vary the opacity of a whole tree of differently-colored objects, and for that you can use the `opacity` command: + +```swift +opacity 0.5 +group { + color 1 0 0 // opaque red + cube // 50% transparent red cube + color 0 1 0 // opaque green + sphere // 50% transparent green sphere +} +``` + +Setting `opacity` affects all subsequently defined objects up until the end of the current [scope](scope.md). The `color` or `texture` of each object is multiplied by the current `opacity` value, so a `color` with alpha 0.5 combined with an `opacity` of 0.5 will result in the object being drawn with an overall opacity of 0.25. + +Opacity is applied hierarchically. The value you specify with the `opacity` command is multiplied by the value set in the parent scope, so drawing an object with `opacity` 1.0 inside a scope with `opacity` 0.5 will result in in an opacity of 0.5, not 1.0, because the inner value is relative to the outer value. + +As with the alpha property of `color`, `opacity` is measured in the range 0 (fully transparent) to 1 (fully opaque), however you can specify `opacity` values higher than 1. This can be useful for making an object *more* opaque than the base level for its current scope. For example, by setting `opacity` to 1 / [the parent scope opacity], you can cancel it out: + +```swift +opacity 0.5 +group { + color 1 0 0 // opaque red + cube // 50% transparent red cube + color 0 1 0 // opaque green + opacity 2 // cancel out the 50% opacity + sphere // opaque green sphere +} +``` + +--- +[Index](index.md) | Next: [Transforms](transforms.md) diff --git a/docs/1.7.1/mac/meshes.md b/docs/1.7.1/mac/meshes.md new file mode 100644 index 00000000..c78cc732 --- /dev/null +++ b/docs/1.7.1/mac/meshes.md @@ -0,0 +1,128 @@ +Meshes +--- + +While they may appear solid, the 3D shapes that ShapeScript produces are actually hollow shells composed of polygons. These polygon shells are known as [meshes](https://en.wikipedia.org/wiki/Polygon_mesh). + +ShapeScript provides many tools for constructing meshes, including [primitives](primitives.md), [builders](builders.md) and [CSG](csg.md) operations, but you can also create meshes manually by specifying each polygon yourself. While this is generally a tedious approach compared to the other tools, it affords you complete control over the mesh topology, and allows you to create arbitrary shapes that might be hard to achieve with higher-level tools. + +## Polygons and Points + +A mesh is made up of one or more *polygons*, and each polygon is made up of three or more [vertices](https://en.wikipedia.org/wiki/Vertex_(geometry)) or *points*. To construct a mesh you use the `mesh`, `polygon` and `point` commands. The following code defines the simplest possible mesh - a single triangle: + +```swift +mesh { + polygon { + point 0 0 + point 1 0 + point 1 1 + } +} +``` + +![Triangle](../../images/triangle-polygon.png) + +The `point` command accepts a [vector](literals.md#vectors-and-tuples) value. Polygons can be placed anywhere in three-dimensional space, so `point` accepts up to three values (for the X, Y and Z coordinates respectively). In this case we've just specified two values, so the Z coordinate defaults to zero. + +Manually constructed meshes do not currently support textures, but they inherit the current [material color](materials.md#color). Color can also be applied separately to individual vertices within a mesh. When vertices of a given polygon have different colors applied, the color will be smoothly interpolated between them, creating a gradient: + +```swift +mesh { + polygon { + color red + point 0 0 + color green + point 1 0 + color blue + point 1 1 + } +} +``` + +![Colored triangle](../../images/colored-triangle.png) + +## Watertightness + +If you rotate the triangle you will see that the back face is invisible. Most of the mesh-construction commands in ShapeScript produce shapes that are *watertight*, meaning that they do not contain any holes that would allow you to see the back faces of the polygon surface, but when you create a mesh using the `mesh` command it's up to you to ensure that the mesh you create is watertight. You can check this by selecting the mesh in the editor and [getting info](getting-started.md#debugging-and-selection). As you can see, our triangle is not watertight: + +![Not watertight](../../images/not-watertight.png) + +Watertightness is an important quality when using [CSG](csg.md) operations, because the "solid" in Constructive Solid Geometry implies that shapes are expected to behave like solid objects (even though they are actually hollow), and holes or exposed back faces will cause glitches as ShapeScript is unable to determine whether a given point lies inside or outside the shape. + +To solve this, we can add another triangle with the same vertices but facing the opposite direction. But wait - we never specified the direction of the triangle face in the first place, so how does ShapeScript decide which way a polygon is facing? + +## Winding order + +By convention, polygons in ShapeScript are assumed to be defined with [counterclockwise](https://en.wikipedia.org/wiki/Counterclockwise) (aka *anticlockwise*) winding. What that means is that when looking at the polygon from the front, the vertices will be ordered in counterclockwise direction. To add a back face we'll create a second triangle with the inverse vertex order: + +```swift +mesh { + // Front + polygon { + color red + point 0 0 + color green + point 1 0 + color blue + point 1 1 + } + // Back + polygon { + point 1 1 + point 1 0 + point 0 0 + } +} +``` + +(We've left the back face white in this case, so you can tell which side is which). If you get info again you'll see that the triangle is now watertight: + +## Procedural Meshes + +We've learned how to make a triangle, but what about something a bit more complex? For our next trick we're going to create a cube. + +A cube has six faces, each consisting of four vertices - 24 vertices in total. But in fact there are really only eight unique vertices, so rather than tediously typing out the same points over and over, maybe we can use the power of scripting to save ourselves some effort? + +First we'll define a [block](blocks.md) called "side" that creates a single side of the cube: + +```swift +define side { + color rnd rnd rnd + polygon { + point -0.5 -0.5 0.5 + point +0.5 -0.5 0.5 + point +0.5 +0.5 0.5 + point -0.5 +0.5 0.5 + } +} +``` + +Note that the `color rnd rnd rnd` - this will produce a different random color each time the block is called, making the cube a little more interesting to look at. + +Next we'll call `side` block six times from inside a `mesh`, passing a different orientation each time: + +```swift +mesh { + side { orientation 0 } + side { orientation 0 0.5 } + side { orientation 0 1 } + side { orientation 0 1.5 } + side { orientation 0 0 0.5 } + side { orientation 0 0 -0.5 } +} +``` + +And voila! A colored cube: + +![Cube mesh](../../images/cube-mesh.png) + +Obviously this is a lot more trouble than just using the [cube](primitives.md#cube) command, but hopefully you can see the potential for creating more interesting shapes. + +## Concave and Non-planar Polygons + +In many 3D engines, meshes must be constructed exclusively from triangles. The reason for this is that by definition a triangle is always both [convex](https://en.wikipedia.org/wiki/Convex_polygon) and *planar*, meaning that all its vertices lie on the same plane, which simplifies the mathematics needed to [rasterize](https://en.wikipedia.org/wiki/Rasterisation) or render the mesh. + +ShapeScript places no such restrictions on the polygons that you define in your mesh. Non-planar or non-convex polygons will be automatically split into their constituent triangles as needed for [export](export.md) or display. + +--- +[Index](index.md) | Next: [Paths](paths.md) + diff --git a/docs/1.7.1/mac/options.md b/docs/1.7.1/mac/options.md new file mode 100644 index 00000000..ce87ccd5 --- /dev/null +++ b/docs/1.7.1/mac/options.md @@ -0,0 +1,102 @@ +Options +--- + +All 3D shapes in ShapeScript have a common set of options that you can configure. As implied by the name, options are *optional* - they always have sensible default values that will be used if you don't specify a value. + +Some shapes have extra options, specific to that shape. Later you will learn how to define custom options on your own shapes using the [option command](blocks.md#options). + +An option is denoted by a name followed by one or more values or expressions inside a shape [block](blocks.md). Different options accept different value types, but typically these will be a number, vector or text. Here are some examples: + +```swift +cube { + detail 5 // a numeric value + position 1 0 -1 // a vector value + texture "Earth.png" // a texture value +} +``` + +## Name + +The `name` option allows you to assign a name to a given shape or group, for example: + +```swift +cylinder { + name "Wheel" + size 1 1 0.1 +} +``` + +The name can contain spaces or punctuation, and is wrapped in double quotes to prevent ambiguity with other symbols (see [literals](literals.md) for details). + +The name can be useful for identifying distinct shape components when [selecting them](getting-started.md#debugging-and-selection) in the viewer, or when importing a model exported from ShapeScript into another application (see the [export](export.md) section for details). + +## Detail + +As discussed in the [getting started](getting-started.md) and [primitives](primitives.md) sections, curved shapes cannot be represented exactly using triangles, so they must be approximated to a specified level of detail. + +ShapeScript allows you to configure that detail setting using the `detail` command: + +```swift +sphere { detail 32 } +``` + +Unlike `name`, `detail` is not actually an option, but a global command. You can change detail level at any point within your ShapeScript file, and it will affect all shapes defined subsequently up to the end of the current [scope](scope.md). + +The detail level can be overridden hierarchically, so a `detail` command inside a shape will take precedence over a `detail` command in its containing scope: + +```swift +detail 8 + +sphere { detail 32 } // has detail of 32 + +cylinder { position 1 } // has detail of 8 +``` + +![Detail](../../images/detail.png) + +The `detail` command accepts a single integer value, which represents the number of straight sections used to approximate a circle. This is directly applicable to shapes that have circular sections, such as a sphere or cylinder, as well as to circular [paths](paths.md). + +For curved shapes that are not circular, such as a custom [path](paths.md), the relationship between the `detail` value and the number of sections is not quite so straightforward, but typically 1/4 of the `detail` value will be applied to each curved section of the path. + +## Smoothing + +Similar to `detail`, `smoothing` is used to control the appearance of curved shapes. + +As mentioned above, all shapes in ShapeScript are formed from flat triangles. To create the illusion of a curved surface, lighting can be smoothly interpolated across polygon faces to give the appearance of curvature. Lighting is calculated using [surface normals](https://en.wikipedia.org/wiki/Normal_(geometry)) - vectors pointing outwards from each [vertex](https://en.wikipedia.org/wiki/Vertex_(geometry)) in a mesh that are used to indicate when the simulated curvature differs from the geometric reality. + +[Primitive shapes](primitives.md) in ShapeScript all have appropriate normals set by default, and when creating [paths](paths.md) you can use the `curve` command to specify when a corner should appear curved rather than sharp. However, there are times when you may wish to override the default behavior, e.g. to deliberately create a more angular appearance, or to smooth an [imported](import.md) model that does not already include appropriate surface normal data. + +The `smoothing` command accepts a numeric value in the range 0 to 1. This represents an angle between 0 and 180 degrees (see the [trigonometry section](functions.md#trigonometry) for more about how angles are represented in ShapeScript). This angle is the threshold at which ShapeScript will apply normal-based smoothing. Edges that meet at a greater angle than this threshold will be rendered as a sharp seam, and those that meet at lesser angle will appear as a smooth curve. + +On that basis, a `smoothing` value of 0 means all edges will appear sharp. A value of 1 means all edges will appear rounded (this may look a little strange). A value of 0.5 (90 degrees) means that [obtuse](https://en.wikipedia.org/wiki/Angle#Types_of_angles) edges will appear rounded and acute ones will appear sharp. + +Like `detail`, `smoothing` is a global option that applies hierarchically. You set it once at the top of the file, individually inside each shape, or any combination: + +```swift +smoothing 0 // flat shading + +cylinder { smoothing 0.5 } // smooth shading + +sphere { position 1 } // inherits flat-shading from file scope +``` + +![Smoothing](../../images/smoothing.png) + +## Transform + +Every shape has a position, orientation and size in space. Collectively, these are known as the shape's *transform*, and they can be set using the following options: + +- `position x y z` +- `orientation roll yaw pitch` +- `size width height depth` + +For more information on how these are used, see the [transforms](transforms.md) section. + +## Material + +You can set the color and texture of a shape (collectively known as its *material*) by using the `color` and `texture` options. Like `detail`, `color` and `texture` are not actually options, but global commands that can be set anywhere in your ShapeScript file and will apply to all subsequent shapes within the current [scope](scope.md). + +For more information about materials, see the [materials](materials.md) section. + +--- +[Index](index.md) | Next: [Materials](materials.md) diff --git a/docs/1.7.1/mac/paths.md b/docs/1.7.1/mac/paths.md new file mode 100644 index 00000000..773e83fd --- /dev/null +++ b/docs/1.7.1/mac/paths.md @@ -0,0 +1,313 @@ +Paths +--- + +A path is a sequence of line segments or curves joined end-to-end. The points of the path can be positioned anywhere in 3D space, and the path can be open-ended or closed. + +Paths are not typically used directly as part of a scene (except possibly for something like a thin rope or antenna), but they can be used to define the contours or profile of a 3D shape (see [builders](builders.md) for details). + +## Points + +As with the [polygons](meshes.md#polygons-and-points) in a mesh, you define a path using a series of points. A path must have at least two points to be visible. The following path defines a short, horizontal line along the X axis. + +```swift +path { + point -1 0 + point 1 0 +} +``` + +![Line](../../images/line.png) + +In the above example we used the `point` command, which accepts a [vector](literals.md#vectors-and-tuples) value. Paths can be three-dimensional, so `point` accepts up to three coordinates, but most paths that you create in practice will be 2D (all points will have a Z value of zero). + +Paths can be open or closed (meaning that the points form an unbroken loop). To create a closed path, the first and last point must have the same position. The following path has four points, but it actually describes a triangle rather than a quadrilateral, because the first and last points are the same: + +```swift +path { + point 0 1 + point -1 -1 + point 1 -1 + point 0 1 +} +``` + +![Triangle](../../images/triangle.png) + +## Curves + +Points are great for defining polygonal shapes, but what about curves? + +Curves in ShapeScript are typically created using [quadratic Béziers](https://en.wikipedia.org/wiki/Bézier_curve). A quadratic Bézier is defined by two end-points and a control point. + +The curve passes through the end-points, but does not pass through the control point. Instead, the control point defines the point of intersection for the tangents of the curve as it passes through the end-points. This sounds complicated, but it's quite straightforward to use in practice. + +To create a smooth curve, use the `point` command to define end points, and the `curve` command to define control points. For example, the following creates a smooth arc: + +```swift +path { + point -1 -1 + curve 0 1 + point 1 -1 +} +``` + +![Arc](../../images/arc.png) + +You may notice that this "smooth" arc is not actually very smooth. Just as [3D meshes](meshes.md) are constructed from flat polygons, curves are approximated using straight lines. You can adjust the smoothness of curves in a path using the `detail` command: + +```swift +path { + detail 99 + point -1 -1 + curve 0 1 + point 1 -1 +} +``` + +![Smooth arc](../../images/smooth-arc.png) + +If multiple `curve` commands (control points) are used in sequence, an end-point will be interpolated at the mid-point between them. This allows you to easily create complex curves such as an "S" shape. + +You can also create closed paths entirely using `curve` points. Eight `curve` points arranged in an octagon can closely approximate a circle (the ninth point is just a duplicate of the first, to close the path): + +```swift +path { + curve -0.414 1 + curve 0.414 1 + curve 1 0.414 + curve 1 -0.414 + curve 0.414 -1 + curve -0.414 -1 + curve -1 -0.414 + curve -1 0.414 + curve -0.414 1 +} +``` + +![Circle](../../images/octocircle.png) + +## Arcs + +Arcs created by Bézier curves can approximate a circle, but not perfectly. If you need a precisely circular curve, you can use the `arc` command: + +```swift +arc { + angle 0.5 +} +``` + +The `angle` option defines the angular span of the arc, starting clockwise from the Y axis. The angle is measured in *half-turns* (as explained in the [Transforms](transforms.md#orientation) section) so the `0.5` in the example above produces a quarter circle: + +![Quarter Circle](../../images/quarter-circle.png) + +To adjust the starting angle, you can use the [orientation option](transforms.md#orientation) or [rotate command](transforms.md#relative-transforms) as you would for any other shape. The following creates an arc from -45 degrees to +90 degrees: + +```swift +arc { + orientation -0.25 + angle 0.75 +} +``` + +Arcs have a default radius of 0.5 units. You can increase or decrease this using the `size` option: + +```swift +arc { + size 2 // radius of 1 unit (0.5 * 2) +} +``` + +Arcs can be used in conjunction with individual path points or other arcs to make more complex shapes. The following creates a slab with curved corners: + +```swift +extrude path { + // left arc + arc { + angle -0.5 + } + // bottom-left corner + point -0.5 0 + // bottom-right corner + point 1.5 0 + // right arc + arc { + position 1 0 + orientation 0.5 + angle -0.5 + } + // close path with smooth join + curve 0 0.5 +} +``` + +![Curved slab](../../images/curved-slab.png) + +## Circles + +If you need a complete circle, there's an even simpler way than using `arc`, which is to use the `circle` command: + +```swift +circle +``` + +Like the `sphere` primitive, the `circle` path has a default diameter of 1 unit, and its smoothness is controlled by the current `detail` setting, but these can both be overridden by passing explicit `size` and `detail` options: + +```swift +circle { + size 2 + detail 64 +} +``` + +## Rectangles + +To create a square or rectangle you can use the `square` command: + +```swift +square +``` + +Like `circle`, `square` accepts a `size` option. The following will create a rectangle exactly twice as wide as it is tall: + +```swift +square { + size 2 1 +} +``` + +![Rectangle](../../images/rectangle.png) + +You can even create a rectangle with rounded corners using the `roundrect` command: + +```swift +roundrect +``` + +In addition to `size`, `roundrect` also accepts a `radius` option, which controls the corner radius: + +```swift +roundrect { + size 1 + radius 0.25 +} +``` + +![Roundrect](../../images/roundrect.png) + +## Regular Polygons + +When used outside of a `mesh`, the `polygon` command can be used to create a regular polygon. The following creates a pentagon: + +```swift +polygon { + sides 5 +} +``` + +![Pentagon](../../images/pentagon.png) + +The output for this is similar to using the `circle` command with a specific detail value: + +```swift +circle { + detail 5 +} +``` + +The difference is only apparent if you extrude the result, because in the circle's case the extruded sides are [smoothed](options.md#smoothing) by default, whereas for the polygon they are sharply defined: + +```swift +extrude polygon { + position -1 + sides 6 +} + +extrude circle { + position 1 + detail 6 +} +``` + +![Polygon vs circle](../../images/polygon-vs-circle.png) + +## Procedural Paths + +The `circle` command is fine for creating complete circles, but what if you want a circular arc or semicircle? Calculating the Bézier control points for that manually would be tedious, so this is where the power of ShapeScript's procedural logic comes in handy. + +To create a path procedurally, you can use a [for loop](control-flow.md#loops) along with [relative transform commands](transforms.md#relative-transforms) such as `rotate`. For example, the following code generates a semicircle: + +```swift +path { + for 0 to 8 { + curve 0 1 + rotate 1 / 8 + } +} +``` + +![Semicircle](../../images/semicircle.png) + +## Nested Paths + +A path can be formed from multiple distinct sub-paths. To add additional sub-paths, you can nest a `path` command inside another one (or any other command that returns a path). The following code creates a path made up of two overlapping circles: + +```swift +path { + circle + translate 0.5 + scale 0.5 + circle +} +``` + +![Overlapping Circles](../../images/overlapping-circles.png) + +## Path Colors + +Paths cannot currently be textured, but they inherit the current [material color](materials.md#color). Color can also be applied to individual points within a path. When neighboring path points have different colors applied, the color will be smoothly interpolated between them, creating a gradient: + +```swift +path { + color red + point 0 1 + color green + point -1 -1 + color blue + point 1 -1 +} +``` + +![Path colors](../../images/path-colors.png) + +## SVG Paths + +[Scalable Vector Graphics (SVG)](https://en.wikipedia.org/wiki/Scalable_Vector_Graphics) is a popular standard for defining vector graphics. SVG is a fairly complex XML-based format, but a subset of the SVG standard defines a simple text-based [path syntax](https://www.w3.org/TR/SVG11/paths.html#PathData) that uses a series of single-character instructions, each followed by one or more coordinates to define a path. For example, the following SVG path code defines a triangle: + +```svg + +``` + +You can use this syntax in ShapeScript by taking just the `d` attribute from the path and passing it to the `svgpath` command, as follows: + +```swift +fill svgpath "M 100 100 L 300 100 L 200 300 z" +``` + +You can fill or extrude an `svgpath` and adjust its size, position, etc. as follows: + +```swift +fill svgpath { + size 0.01 + position -2 2 + "M 100 100 L 300 100 L 200 300 z" +} +``` + +Which produces the following output: + +![SVG Triangle](../../images/svgpath.png) + +**Note:** SVG uses a flipped vertical coordinate system relative to that used by ShapeScript. To compensate for this, vertical (Y) coordinates passed to the `svgpath` command are treated as negative. + +--- +[Index](index.md) | Next: [Text](text.md) diff --git a/docs/1.7.1/mac/primitives.md b/docs/1.7.1/mac/primitives.md new file mode 100644 index 00000000..302db1e8 --- /dev/null +++ b/docs/1.7.1/mac/primitives.md @@ -0,0 +1,107 @@ +Primitives +--- + +ShapeScript has a number of built-in shapes that you can use to quickly construct a scene. These are known as *primitives*, to distinguish them from more complex custom shapes that you can define using [builders](builders.md) or [CSG](csg.md) operations. + +## Cube + +The `cube` primitive creates a box. You can create a cube just by using the `cube` command, which defines a cube with the default size of one unit: + +```swift +cube +``` + +To alter the size of the cube, you can pass a [block](blocks.md) containing a `size` option followed by up to 3 values specifying the individual dimensions of the cube, or a single value to create a cube with equal sides. + +```swift +cube { size 1 2 } +``` + +![Box](../../images/box.png) + +You can also rotate and position the cube using the `orientation` and `position` options, as follows: + +```swift +cube { + size 1 1 2 + position 1 0 0 + orientation 0.25 +} +``` + +The `size`, `position` and `orientation` options are common to all shapes. For more information about these (and other) options, see the [options](options.md) section. + +## Sphere + +The `sphere` primitive creates a spherical ball. Again, `size` can be used to control the diameter. The following creates a sphere with a diameter of 1 unit (which is the default). + +```swift +sphere { size 1 } +``` + +![Sphere](../../images/sphere.png) + +You may notice that the sphere doesn't look very smooth. As mentioned in the [getting started section](getting-started.md), 3D shapes in ShapeScript are made up of polygons, so curves cannot be represented exactly, only approximated. + +You can improve the quality of the sphere by using the `detail` option: + +```swift +sphere { + detail 32 + size 1 +} +``` + +![Smoother sphere](../../images/smoother-sphere.png) + +You can also pass multiple parameters to `size` to create a *spheroid*: + +```swift +sphere { + detail 32 + size 1 2 3 +} +``` + +![Spheroid](../../images/spheroid.png) + +## Cylinder + +The `cylinder` primitive creates a flat-ended cylinder. + +```swift +cylinder { size 1 } +``` + +![Cylinder](../../images/cylinder.png) + +Like the `sphere` primitive, `cylinder` uses the `detail` command to control its smoothness. If you require a cylinder that is longer or shorter, you can pass a second parameter to the `size` to control the diameter and length independently: + +```swift +cylinder { + detail 64 + size 1 2 +} +``` + +## Cone + +The `cone` primitive creates a conical shape: + +```swift +cone { size 1 } +``` + +![Cone](../../images/cone.png) + +Like `sphere` and `cylinder`, its smoothness is controlled by the `detail` command, and the diameter and length can be controlled independently by passing additional parameters to `size`: + +```swift +cone { + detail 48 + size 1 2 +} +``` + +--- +[Index](index.md) | Next: [Options](options.md) diff --git a/docs/1.7.1/mac/scope.md b/docs/1.7.1/mac/scope.md new file mode 100644 index 00000000..105d6422 --- /dev/null +++ b/docs/1.7.1/mac/scope.md @@ -0,0 +1,25 @@ +Scope +--- + +## Block Scope + +ShapeScript programs are hierarchical, with `{ ... }` braces used to denote the start and end of a block of related options or commands. + +When you define symbols, the effect is limited to the *scope* of the current block, meaning that once the block exits (i.e. when the closing `}` is reached), and symbols defined inside it are discarded, and symbols whose values were overridden inside the block will revert to the value they had before the block was entered. + +Block scope is not just limited to symbols; it also affects [materials](materials.md) and [transforms](transforms.md). When you use the `translate`, `rotate` and `scale` commands to modify the world-transform, or use `color` or `texture` to modify the current material, the effect of those changes is limited to the current block. + +This is convenient, because it means that code that defines subcomponents in your scene can use transforms and materials internally without them "leaking out" and affecting other objects. + +## Function Scope + +When you define a [custom function](functions.md#custom-functions), it also creates a local scope around its body. Like blocks, functions inherit symbols from their parent scope and locally defined symbols will not *leak* out into their parent scope. Unlike blocks, functions can affect the transforms and materials of the scope where they are invoked. + +## Conditional Scope + +Control flow statements like [for loops](control-flow.md#loops) and [if statements](control-flow.md#if-else) also creates a local scope around their body. Like function scope, loop and if/else scope does not apply to transforms or materials, but only to symbols created using the `define` command. + +Any symbols defined inside a `for` loop will be restricted to the inside of the loop body. This also applies to the optional loop index variable. + +--- +[Index](index.md) | Next: [Debugging](debugging.md) diff --git a/docs/1.7.1/mac/symbols.md b/docs/1.7.1/mac/symbols.md new file mode 100644 index 00000000..f563e77a --- /dev/null +++ b/docs/1.7.1/mac/symbols.md @@ -0,0 +1,51 @@ +Symbols +--- + +A symbol is a named value (sometimes referred to as a *constant*) that can be used in place of a [literal](literals.md) value. + +ShapeScript includes several built-in symbols such as `detail` (the current level of detail), or `pi` (the mathematical constant used to compute angles), but you can also define your own symbols using the `define` command: + +```swift +define sides 5 +define red 1 0 0 +``` + +Symbol names consist of a letter followed by zero or more letters, numbers or underscore (`_`) characters. Symbols cannot begin with a number or underscore, and spaces or other punctuation are not allowed. Symbols are case-sensitive, and by convention should begin with a lowercase letter. For multi-word symbols, the recommended convention is to capitalize the first letter of each new word (known as *camelCase* because the capital letters form "humps" in the back of the word): + +```swift +define numberOfSides 7 +``` + +The value assigned to a symbol can be a literal or an [expression](expressions.md). Once defined, a symbol can be used anywhere in place of a literal value, such as inside an expression, or as a [command](commands.md) or [function](functions.md) parameter. Symbols can also be used in the definition of other symbols: + +```swift +define three 3 +define two 2 +define five three + two +``` + +Symbols help to make your ShapeScript file more readable by assigning meaningful names to otherwise inscrutable literal values. They also make the script easier to modify and maintain by avoiding duplication of literal values throughout the code. + +Existing symbols can be redefined by calling `define` again with the same name. If a symbol is defined inside a `{ ... }` block then it will be [scoped](scope.md) to the code inside that block (meaning that it cannot be used after the closing `}`): + +```swift +for i in 1 to 5 { + define foo i // define symbol foo with the current loop index value +} +// foo is undefined here +``` + +Symbols can be *shadowed*, meaning that a symbol defined in an outer scope can be redefined inside an inner scope, but the symbol will revert to its original definition when the scope ends. It is currently only possible to create *constants*, you cannot create *variables* (symbols whose value can be changed later): + +```swift +define foo 1 +for i in 1 to 5 { + define foo i // redefine foo with the current loop index value +} +// foo reverts to original value of 1 after loop terminates +``` + +**Note:** Because of this scoping, it's not possible to conditionally define a value inside an `if` statement and use it outside the `if`. For the correct way to do this, see [conditional defines](control-flow.md#conditional-defines). + +--- +[Index](index.md) | Next: [Expressions](expressions.md) diff --git a/docs/1.7.1/mac/text.md b/docs/1.7.1/mac/text.md new file mode 100644 index 00000000..891a970b --- /dev/null +++ b/docs/1.7.1/mac/text.md @@ -0,0 +1,223 @@ +Text +--- + +The `text` command can be used to generate individual words, lines, or whole paragraphs of text. You use the `text` command as follows: + +```swift +text "Hello, World!" +``` + +To create multiline text you can use the `\n` line-break sequence: + +```swift +text "The quick brown fox\njumps over the lazy dog" +``` + +Or place each line of text on its own line within the file, surrounded by quotes: + +```swift +text { + "The quick brown fox" + "jumps over the lazy dog" +} +``` + +The output of the `text` command is a series of [paths](paths.md), one for each character or *glyph* in the text: + +![Text](../../images/text.png) + +You can use the `fill` or `extrude` commands to turn these paths into a solid mesh (see [builders](builders.md) for details): + +![Solid Text](../../images/solid-text.png) + +## Interpolation + +For your convenience, the text command will automatically convert numeric values to their character representation. The following displays the numbers 1 to 5 as 3D text: + +```swift +for i in 1 to 5 { + extrude text i + translate 1 +} +``` + +![Numbers](../../images/numbers.png) + +You may find that in some cases you need to compose some text dynamically from several source values. ShapeScript has a feature called [string interpolation](https://en.wikipedia.org/wiki/String_interpolation), whereby values can be inserted into a larger body of text. + +To use string interpolation, simply pass multiple values to the `text` command and they will be concatenated together: + +```swift +define apples 5 +define pears 3 +text "Bob has " apples " apples, and " pears " pears" +``` + +Text values are concatenated without spaces, so if you require spaces you should include them explicitly inside quotes: + +```swift +text "Good" "bye" // becomes "Goodbye" +text "Hello " "World" "!" // becomes "Hello World!" +``` + +For your convenience, non-text values will be spaced-out automatically. If you wish to remove the spaces, you can place an empty string literal between them: + +```swift +text 1 2 3 // becomes "1 2 3" +text 1 "" 2 "" 3 // becomes "123" +``` + +**Note:** Spaces *between* values make no difference to the output, so feel free to space them as you wish. These are all equivalent: + +```swift +text "We're number "1"!" +text "We're number " 1"!" +text "We're number " 1 "!" +text "We're number " 1 "!" +``` + +## Wrap Width + +By default, text is laid out on a single line unless you explicitly add line-breaks, either by splitting the text into multiple literals or by adding `\n` escape sequences. + +But by using the `wrapwidth` option, you can force text to wrap automatically to fit a specified width. This is useful when working with programatically generated text, where it may be cumbersome to have to add logic to insert linebreaks at the right point. + +```swift +text { + wrapwidth 3 + "Hello, World!" +} +``` + +![Wrapped text](../../images/text-wrap.png) + +**Note:** `wrapwidth` is specified in world units, not characters. + +## Size and Line Height + +By default, text has a line height of one world unit. The line height is not the height of the actual characters, but the distance between two consecutive lines of text. If you imagine some lined writing paper, the line height would be the distance between the lines. + +To adjust the text size, you can use the [size](transforms.md#size) option: + +```swift +text { + size 2 // increase text size by 200% + "Hello, World!" +} +``` + +The size in this case actually refers to the line height, so setting a size of 2 increases the line height to 2 (and increases the actual size and spacing of the characters in proportion to this height). + +You can resize the text non-uniformly by passing separate width and height values for the size: + +```swift +text { + size 2 1.5 // set width to 200% and height to 150% + "Hello, World!" +} +``` + +Alternatively, for extruded text, you can set the size in the shape block instead, which allows you to also set the depth at the same time: + +```swift +extrude { + size 2 2 0.5 // 200% sized text, with 50% depth + text "Hello, World!" +} +``` + +## Line Spacing + +By default, text has a line spacing of zero, meaning that consecutive lines of text with size 1 will be spaced exactly one unit apart, with no extra padding. You can increase or decrease this line spacing using the `linespacing` property. A value of 0.5 will add an additional space of 0.5 world units between consecutive lines: + +```swift +text { + linespacing 0.5 + "Hello," + "World!" +} +``` + +![+50% Line spacing](../../images/linespacing-increased.png) + +A value of -0.5 will reduce the spacing by 0.5 (causing the lines to overlap by 0.5 units): + +```swift +text { + linespacing -0.5 + "Hello," + "World!" +} +``` + +![+50% Line spacing](../../images/linespacing-decreased.png) + +## Position and Orientation + +To adjust the text position and orientation, use the [position](transforms.md#position) and [orientation](transforms.md#orientation) commands: + +```swift +text { + position 2 1 // move text 2 units to the right and 1 unit up + orientation 0.5 // rotate by 90 degrees + "Hello World" +} +``` + +Or for filled or extruded text, you can set these on the containing shape block instead: + +```swift +fill { + position 1 2 3 // set the position in 3D space + orientation 0 0.25 0 // rotate around the Y axis + text "Hello World" +} +``` + +Unlike most shapes, which are positioned relative to their center, text is positioned relative to its left margin and baseline. + +![Default text alignment](../../images/text-default.png) + +In order to center a piece of text, you will need to know its actual dimensions. For this you can use the [bounds](bounds.md) member property. The following code uses the bounds to center some text: + +```swift +define hello text "Hello" +translate -hello.bounds.width/2 -hello.bounds.height/2 +fill hello +``` + +![Centered text](../../images/text-centered.png) + +## Font + +To adjust the text font, you can use the `font` command. like `color` and other [material](materials.md) properties, `font` can be placed either inside the `text` block, or anywhere before it in the same scope: + +```swift +font "Zapfino" +fill text "Hello World" +``` + +![Text with font](../../images/text-font.png) + +**Note:** Some fonts are inherently much more detailed than others, and may take a considerable time to generate. You may want to set the [detail](options.md#detail) option to a lower value for text than you would for other geometry. + +The font name you provide must match a font that is already installed on your system. If no matching fonts are found then an error will be raised. If you wish to load a font file directly, without installing it, you can pass the filename or path to the `font` command instead of the font name: + +```swift +font "filename.ttf" +``` + +Only fonts with a ".ttf", ".otf" or ".ttc" file extension are supported. The extension is required, or the `font` parameter will be treated as a system font rather than a file. If a relative path or filename is used, it should be specified relative to the ShapeScript file that references it. + +The filename can be constructed dynamically by using the [string interpolation](text.md#interpolation) feature, which is sometimes useful if, for example, you have multiple font files with a common prefix or suffix: + +```swift +for n in 1 to 5 { + texture "font" n ".ttf" + text "Hello, World!" + translate 0 -1 0 +} +``` + +--- +[Index](index.md) | Next: [Builders](builders.md) diff --git a/docs/1.7.1/mac/transforms.md b/docs/1.7.1/mac/transforms.md new file mode 100644 index 00000000..88ae6b44 --- /dev/null +++ b/docs/1.7.1/mac/transforms.md @@ -0,0 +1,110 @@ +Transforms +--- + +Every shape has a position, orientation and size. These can be set in *absolute* units by using the `position`, `orientation` and `size` options, or in *relative* units using the `translate`, `rotate` and `scale` commands (see [relative transforms](#relative-transforms) below). + +## Position + +When you define a shape in ShapeScript, it is created at the *point of origin*. By default, this is the zero position of the X, Y and Z axes within the current [scope](scope.md), but this can be overridden using the `position` option. + +The `position` option accepts a [vector](literals.md#vectors-and-tuples) of up to 3 values representing a coordinate along the X Y and Z axes. If values are omitted they are assumed to be zero. The default `position` of every shape is `0 0 0`, which is located at the *origin*, aka the center of the world. + +Positive `position` values move the shape right, up, and towards the camera respectively. Negative values move it left, down and away from the camera: + +```swift +cube { + position 0 0 5 // moves the cube 5 units towards the camera +} +``` + +Positions are applied hierarchically. For shapes located at the root of the ShapeScript file, their `position` is relative to the world origin, however you can nests shapes inside [groups](groups.md), in which case the `position` of the child shapes will be measured relative to the `position` of their containing group. + +## Orientation + +The `orientation` option defines the rotation for the shape using three parameters called `roll`, `yaw` and `pitch`. The `roll` value represents a rotation around the Z axis, the `yaw` is rotation around the Y axis, and the `pitch` is a rotation around the X axis: + +```swift +cube { + orientation 0 0.25 0 // rotates the cube 45 degrees around the Y axis +} +``` + +The ordering of these three parameters may seem counterintuitive (Z, Y, X), but it makes sense in the context that when working with 2D [paths](paths.md), you often wish to apply a rotation only around the Z axis (in the XY plane), and by having that as the first parameter, you can simply omit the other values, which will default to zero. + +Angles of rotation are specified as numbers in the range 0 to 2 (or 0 to -2 if you prefer), representing the number of [half-turns](https://en.wikipedia.org/wiki/Turn_(angle)). This again may seem odd if you were expecting degrees or radians, but using this range works better mathematically, as you avoid the need to multiply or divide computed rotation values by [pi](https://en.wikipedia.org/wiki/Pi) or 180, and it's easier to mentally convert 90 degrees to 0.5 than to 1.5707963268 (see the [trigonometry section](functions.md#trigonometry) for more about converting between angular representations). + +While it is relatively simple to use the `orientation` property to specify a rotation around a single axis, it can be awkward to apply a rotation around multiple axes at once due to the fixed order. If you need to do that, you may find it simpler to use the `rotate` command instead (described [below](#relative-transforms)), which can be applied multiple times in any order. + +## Size + +The `size` option applies a scaling factor to all subsequent geometry. A scale factor of `0.5 0.5 0.5` for example, would halve the size of all subsequent shapes, as well as halving the offset applied by subsequent `translate` commands. + +Like the `position` and `orientation` commands, `size` allows you to omit one or two parameters, however unlike the other commands, omitted parameters are assumed to be equal to the first value given. So `size 0.5` is equivalent to `size 0.5 0.5 0.5`. + +It it possible to apply a negative scale factor, which has the effect of flipping the geometry. For example, `size 1 -1 1` would flip all subsequent shapes upside-down along the Y axis. This does not always work as intended however, and may produce odd side-effects such as turning shapes inside-out. In general it is better to stick to positive `size` values, and use `orientation` if you need to flip a shape around. + +## Relative Transforms + +When defining paths or shapes procedurally using [loops](control-flow.md#loops) or other logic, you will often wish to position shapes or points using *relative* coordinates, rather than absolutely. You can do this using the `translate`, `rotate` and `scale` commands, which are counterparts to the `position`, `orientation` and `size` options. + +Translation is the mathematical term for directional movement. Like `position`, `translate` takes up to 3 values representing offsets along the X Y and Z axes. Unlike `position`, the values do not specify the position of the containing shape, but rather they move the *origin* of the current [scope](scope.md), affecting all subsequently defined shapes. + +The two following examples are therefore equivalent: + +```swift +cube { position 1 0 0 } +``` + +```swift +translate 1 0 0 +cube +``` + +In both cases, the cube is moved one unit to the right. But whereas in the first example the cube itself has been moved, in the second example, the *world* has been moved. + +This distinction doesn't matter much until you create another shape. In the first example, the effect of setting the `position` is limited to the cube itself, and subsequent shapes will be unaffected. However in the second example, all subsequent shapes will also be shifted by one unit to the right. + +You can prevent this by using another `translate` to move the origin back to its original position: + +```swift +translate 1 0 0 +cube // located at 1 0 0 +translate -1 0 0 +sphere // located at 0 0 0 +``` + +Just as the `translate` command moves the origin, the `rotate` command rotates it, and the `scale` command increases or decreases the scale factor. These are equivalent: + +```swift +cube { + size 2 + orientation 0.25 +} +``` + +```swift +scale 2 +rotate 0.25 +cube +``` + +And as with `translate`, in the second case the rotation and scale will be permanently altered for all future shapes, so to reset them you would need to apply the inverse transforms: + +```swift +scale 2 +rotate 0.25 +cube +rotate -0.25 +scale 0.5 +``` + +As mentioned in the [orientation](#orientation) section above, an advantage of the `rotate` command is that it allows you to apply rotations in any order. For example the following code applies a pitch of 45 degrees followed by a roll of 80 degrees, which would be very difficult to express as a single `rotate` or `orientation` instruction due to the fixed roll-yaw-pitch order: + +```swift +rotate 0 0 0.25 // pitch 45 degrees +rotate 0.4 0 0 // roll 80 degrees +cube +``` + +--- +[Index](index.md) | Next: [Bounds](bounds.md)