Skip to content

Commit b29c7bc

Browse files
committed
Support setting texture intensity
1 parent 590f111 commit b29c7bc

File tree

7 files changed

+104
-10
lines changed

7 files changed

+104
-10
lines changed

ShapeScript/Interpreter.swift

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1339,9 +1339,10 @@ extension Expression {
13391339
}
13401340
func tupleApply(
13411341
_ fn: (Double, Double) -> Double,
1342+
lhs value: Value? = nil,
13421343
widen: Bool
13431344
) throws -> Value {
1344-
let lhs = try tupleValue(lhs)
1345+
let lhs = try value ?? tupleValue(lhs)
13451346
let rhs = try tupleValue(rhs, index: 1)
13461347
switch (apply(lhs, rhs, fn), lhs) {
13471348
case let (.tuple(values), .tuple(lhs)) where widen:
@@ -1350,15 +1351,31 @@ extension Expression {
13501351
return value
13511352
}
13521353
}
1354+
func tupleOrTextureApply(_ fn: (Double, Double) -> Double) throws -> Value {
1355+
let lhs = try lhs.evaluate(
1356+
as: .union([.number, .list(.number), .texture]),
1357+
for: String(op.rawValue),
1358+
index: 0,
1359+
in: context
1360+
)
1361+
if case let .texture(texture) = lhs {
1362+
guard let texture = texture else {
1363+
return .texture(nil)
1364+
}
1365+
let rhs = try doubleValue(rhs)
1366+
return .texture(texture.withIntensity(fn(texture.intensity, rhs)))
1367+
}
1368+
return try tupleApply(fn, lhs: lhs, widen: false)
1369+
}
13531370
switch op {
13541371
case .minus:
13551372
return try tupleApply(-, widen: true)
13561373
case .plus:
13571374
return try tupleApply(+, widen: true)
13581375
case .times:
1359-
return try tupleApply(*, widen: false)
1376+
return try tupleOrTextureApply(*)
13601377
case .divide:
1361-
return try tupleApply(/, widen: false)
1378+
return try tupleOrTextureApply(/)
13621379
case .modulo:
13631380
return try tupleApply(fmod, widen: false)
13641381
case .lt:

ShapeScript/Members.swift

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,7 @@ extension ValueType {
9797
"alpha": .number,
9898
"bounds": .bounds,
9999
"opacity": .number,
100+
"intensity": .number,
100101
"color": .optional(.color),
101102
"texture": .texture,
102103
"metallicity": .numberOrTexture,
@@ -132,6 +133,8 @@ extension Value {
132133
return ["roll", "yaw", "pitch"]
133134
case .color:
134135
return ["red", "green", "blue", "alpha"]
136+
case .texture:
137+
return ["intensity"]
135138
case .material:
136139
return ["opacity", "color", "texture", "metallicity", "roughness", "glow"]
137140
case let .tuple(values):
@@ -185,7 +188,7 @@ extension Value {
185188
return ["string", "font", "color", "linespacing"]
186189
case let .object(values):
187190
return values.keys.sorted()
188-
case .texture, .boolean, .number, .radians, .halfturns:
191+
case .boolean, .number, .radians, .halfturns:
189192
return []
190193
}
191194
}
@@ -228,6 +231,11 @@ extension Value {
228231
case "alpha": return .number(color.a)
229232
default: return nil
230233
}
234+
case let .texture(texture):
235+
switch name {
236+
case "intensity": return .number(texture?.intensity ?? 0)
237+
default: return nil
238+
}
231239
case let .material(material):
232240
switch name {
233241
case "opacity": return material.opacity.map { .numberOrTexture($0) } ?? .number(1)
@@ -365,7 +373,7 @@ extension Value {
365373
}
366374
case let .object(values):
367375
return values[name]
368-
case .boolean, .texture, .number, .radians, .halfturns:
376+
case .boolean, .number, .radians, .halfturns:
369377
return nil
370378
}
371379
}

ShapeScript/Types.swift

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -341,11 +341,19 @@ extension Value {
341341
if types.contains(where: { self.type.isSubtype(of: $0) }) {
342342
return self
343343
}
344+
var errors = [Error]()
344345
for type in types.sorted() {
345-
if let value = try self.as(type, in: context) {
346-
return value
346+
do {
347+
if let value = try self.as(type, in: context) {
348+
return value
349+
}
350+
} catch {
351+
errors.append(error)
347352
}
348353
}
354+
if let error = errors.first {
355+
throw error
356+
}
349357
return nil
350358
case let (_, .list(type)) where self.type.isSubtype(of: type):
351359
return [self]
@@ -364,8 +372,15 @@ extension Value {
364372
if name.isEmpty {
365373
return .texture(nil)
366374
}
367-
let url = try context?.resolveURL(for: name)
368-
return .texture(.file(name: name, url: url ?? URL(fileURLWithPath: name), intensity: 1))
375+
do {
376+
let url = try context?.resolveURL(for: name)
377+
return .texture(.file(name: name, url: url ?? URL(fileURLWithPath: name), intensity: 1))
378+
} catch {
379+
if URL(fileURLWithPath: name).pathExtension.isEmpty {
380+
return nil
381+
}
382+
throw error
383+
}
369384
}
370385
case let (.tuple(values), .font) where values.contains { $0.type == .string }:
371386
fallthrough

ShapeScriptTests/InterpreterTests.swift

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3452,7 +3452,7 @@ class InterpreterTests: XCTestCase {
34523452
guard case .typeMismatch(
34533453
for: "*",
34543454
index: 0,
3455-
expected: "number or vector",
3455+
expected: "number, texture, or vector",
34563456
got: "tuple"
34573457
)? = error?.type else {
34583458
XCTFail()
@@ -3461,6 +3461,18 @@ class InterpreterTests: XCTestCase {
34613461
}
34623462
}
34633463

3464+
func testTextureIntensityMultiply() throws {
3465+
let program = """
3466+
texture "Stars1.jpg" * 0.5
3467+
print texture
3468+
"""
3469+
let delegate = TestDelegate()
3470+
XCTAssertNoThrow(try evaluate(parse(program), delegate: delegate))
3471+
XCTAssertEqual(delegate.log, [Texture.file(
3472+
name: "Stars1.jpg", url: testsDirectory.appendingPathComponent("Stars1.jpg"), intensity: 0.5
3473+
)])
3474+
}
3475+
34643476
func testNumericTupleScalarAdd() {
34653477
let program = "print (-1 3) + 2"
34663478
let delegate = TestDelegate()

docs/ios/materials.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ The result is shown below. On the left is the normal texture as it appears when
228228

229229
**Note:** you can combine a normal map with an ordinary texture to control both the color and lighting of a surface.
230230

231+
To vary the intensity of the surface perturbation, you can apply a multiplier to the texture. The following code increases the apparent depth of the stones by 2x (you can also reduce the intensity by using a fractional value):
232+
233+
```swift
234+
cube {
235+
normals "cobblestones.png" * 2
236+
}
237+
```
238+
231239
## Opacity
232240

233241
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:
@@ -317,6 +325,12 @@ metallicity "weathered-metal.png"
317325

318326
Using a metallicity texture allows you to create composite surfaces that are only partly metallic, or which have patches of rust, dirt or paint that reduce the shininess. Since metallicity is a scalar rather than color property, the texture should be in grayscale. If a full-color image is supplied, only the red channel will be used.
319327

328+
As with the [normal map](#normals) above, you can apply a multiplier to the metallicity texture to vary its overall intensity:
329+
330+
```swift
331+
metallicity "weathered-metal.png" * 0.5 // half as metallic
332+
```
333+
320334
## Roughness
321335

322336
The `roughness` property counteracts the shininess applied by the [metallicity](#metallicity) property by simulating surface scratches or texture. This doesn't completely negate the effect of metallicity, but it can be used to create surfaces that are recognizably both metallic and also non-smooth, like an old scratched-up piece of iron:

docs/mac/materials.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ The result is shown below. On the left is the normal texture as it appears when
228228

229229
**Note:** you can combine a normal map with an ordinary texture to control both the color and lighting of a surface.
230230

231+
To vary the intensity of the surface perturbation, you can apply a multiplier to the texture. The following code increases the apparent depth of the stones by 2x (you can also reduce the intensity by using a fractional value):
232+
233+
```swift
234+
cube {
235+
normals "cobblestones.png" * 2
236+
}
237+
```
238+
231239
## Opacity
232240

233241
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:
@@ -317,6 +325,12 @@ metallicity "weathered-metal.png"
317325

318326
Using a metallicity texture allows you to create composite surfaces that are only partly metallic, or which have patches of rust, dirt or paint that reduce the shininess. Since metallicity is a scalar rather than color property, the texture should be in grayscale. If a full-color image is supplied, only the red channel will be used.
319327

328+
As with the [normal map](#normals) above, you can apply a multiplier to the metallicity texture to vary its overall intensity:
329+
330+
```swift
331+
metallicity "weathered-metal.png" * 0.5 // half as metallic
332+
```
333+
320334
## Roughness
321335

322336
The `roughness` property counteracts the shininess applied by the [metallicity](#metallicity) property by simulating surface scratches or texture. This doesn't completely negate the effect of metallicity, but it can be used to create surfaces that are recognizably both metallic and also non-smooth, like an old scratched-up piece of iron:

docs/src/materials.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,14 @@ The result is shown below. On the left is the normal texture as it appears when
228228

229229
**Note:** you can combine a normal map with an ordinary texture to control both the color and lighting of a surface.
230230

231+
To vary the intensity of the surface perturbation, you can apply a multiplier to the texture. The following code increases the apparent depth of the stones by 2x (you can also reduce the intensity by using a fractional value):
232+
233+
```swift
234+
cube {
235+
normals "cobblestones.png" * 2
236+
}
237+
```
238+
231239
## Opacity
232240

233241
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:
@@ -317,6 +325,12 @@ metallicity "weathered-metal.png"
317325

318326
Using a metallicity texture allows you to create composite surfaces that are only partly metallic, or which have patches of rust, dirt or paint that reduce the shininess. Since metallicity is a scalar rather than color property, the texture should be in grayscale. If a full-color image is supplied, only the red channel will be used.
319327

328+
As with the [normal map](#normals) above, you can apply a multiplier to the metallicity texture to vary its overall intensity:
329+
330+
```swift
331+
metallicity "weathered-metal.png" * 0.5 // half as metallic
332+
```
333+
320334
## Roughness
321335

322336
The `roughness` property counteracts the shininess applied by the [metallicity](#metallicity) property by simulating surface scratches or texture. This doesn't completely negate the effect of metallicity, but it can be used to create surfaces that are recognizably both metallic and also non-smooth, like an old scratched-up piece of iron:

0 commit comments

Comments
 (0)