@rafaelaugusto/jolt-native 0.1.0-beta.13 → 0.1.0-beta.14

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -6,7 +6,9 @@ Optional NativeScript plugin for `@rafaelaugusto/jolt/native`. Install this pack
6
6
  npm install @rafaelaugusto/jolt @rafaelaugusto/jolt-native
7
7
  ```
8
8
 
9
- Includes compiled scalar/compound playback, MapKit and SceneKit sinks on iOS, and a MapLibre sink on Android. It does not install MapLibre; the app chooses its SDK version. Reference demo: MapLibre Android 11.8.0, NativeScript 9.1, iOS 26 / Android 16. Minimum NativeScript 8.9; native helper APIs require iOS 13+ / Android API 23+ (the app/runtime or SDK may require newer).
9
+ Includes compiled scalar/compound playback, MapKit and SceneKit pose/light/material sinks on iOS, and a MapLibre sink on Android. It does not install MapLibre; the app chooses its SDK version. Reference demo: MapLibre Android 11.8.0, NativeScript 9.1, iOS 26 / Android 16. Minimum NativeScript 8.9; native helper APIs require iOS 13+ / Android API 23+ (the app/runtime or SDK may require newer).
10
+
11
+ SceneKit appearance supports light intensity/color and material transparency/solid diffuse/emission colors; textures are rejected. See the [lighting guide](https://jolt.rafaelaugusto.dev/guide/scene-lighting) for interpolation and lifecycle limits.
10
12
 
11
13
  See the [native properties guide](https://jolt.rafaelaugusto.dev/guide/native-properties) for capabilities, authoring, ownership and execution limits.
12
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rafaelaugusto/jolt-native",
3
- "version": "0.1.0-beta.13",
3
+ "version": "0.1.0-beta.14",
4
4
  "description": "Optional compiled property adapters for Jolt (MapKit, MapLibre and SceneKit).",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "sideEffects": false,
16
16
  "peerDependencies": {
17
- "@rafaelaugusto/jolt": ">=0.1.0-beta.13",
17
+ "@rafaelaugusto/jolt": ">=0.1.0-beta.14",
18
18
  "@nativescript/core": ">=8.9.0"
19
19
  },
20
20
  "nativescript": {
@@ -4,5 +4,13 @@ NS_ASSUME_NONNULL_BEGIN
4
4
  + (JoltPropertySink *)mapCoordinate:(NSObject *)annotation;
5
5
  + (JoltPropertySink *)mapCamera:(NSObject *)mapView;
6
6
  + (JoltPropertySink *)scenePose:(NSObject *)node;
7
+ + (JoltPropertySink *)sceneLightIntensity:(NSObject *)light;
8
+ + (JoltPropertySink *)sceneLightColor:(NSObject *)light;
9
+ + (JoltPropertySink *)sceneMaterialTransparency:(NSObject *)material;
10
+ + (JoltPropertySink *)sceneMaterialDiffuse:(NSObject *)material;
11
+ + (JoltPropertySink *)sceneMaterialEmission:(NSObject *)material;
12
+ /** Diagnostic count of undisposed appearance sinks (native targets are weak). */
13
+ + (NSUInteger)sceneAppearanceSinkCount;
14
+ + (NSUInteger)sceneAppearanceWriteCount;
7
15
  @end
8
16
  NS_ASSUME_NONNULL_END
@@ -1,6 +1,7 @@
1
1
  #import "JoltReferenceProperties.h"
2
2
  #import <MapKit/MapKit.h>
3
3
  #import <SceneKit/SceneKit.h>
4
+ #import <UIKit/UIKit.h>
4
5
 
5
6
  static double longitude(double value) { return fmod(fmod(value+180,360)+360,360)-180; }
6
7
  @interface JoltMapCoordinateSink : JoltPropertySink @end
@@ -47,7 +48,125 @@ static double longitude(double value) { return fmod(fmod(value+180,360)+360,360)
47
48
  [SCNTransaction commit];
48
49
  }
49
50
  @end
51
+ // Appearance is read from the model: SceneKit exposes presentationNode, but no public
52
+ // presentation object for a standalone SCNLight/SCNMaterial. Our immediate writes keep
53
+ // the model current. Apps must remove competing animations before transferring control.
54
+ typedef NS_ENUM(NSUInteger, JoltSceneAppearanceKind) {
55
+ JoltSceneIntensity, JoltSceneLightColor, JoltSceneTransparency, JoltSceneDiffuse, JoltSceneEmission
56
+ };
57
+ static NSUInteger appearanceSinks, appearanceWrites;
58
+ static CGFloat unitComponent(CGFloat value) { return MAX(0, MIN(1, value)); }
59
+ static CGColorRef copySceneColor(id value) CF_RETURNS_RETAINED {
60
+ CGColorRef original = NULL;
61
+ if ([value isKindOfClass:UIColor.class])
62
+ original = [[value resolvedColorWithTraitCollection:UITraitCollection.currentTraitCollection] CGColor];
63
+ else if (value && CFGetTypeID((__bridge CFTypeRef)value) == CGColorGetTypeID())
64
+ original = (__bridge CGColorRef)value;
65
+ if (!original) return NULL;
66
+ CGColorSpaceRef space = CGColorSpaceCreateWithName(kCGColorSpaceSRGB);
67
+ CGColorRef converted = CGColorCreateCopyByMatchingToColorSpace(space, kCGRenderingIntentDefault, original, NULL);
68
+ CGColorSpaceRelease(space);
69
+ if (converted && (CGColorGetNumberOfComponents(converted) != 4 || CGColorSpaceGetModel(CGColorGetColorSpace(converted)) != kCGColorSpaceModelRGB)) {
70
+ CGColorRelease(converted); return NULL;
71
+ }
72
+ return converted;
73
+ }
74
+ @interface JoltSceneAppearanceSink : JoltPropertySink
75
+ @property(nonatomic) JoltSceneAppearanceKind kind;
76
+ @property(nonatomic) BOOL released;
77
+ + (instancetype)target:(NSObject *)target kind:(JoltSceneAppearanceKind)kind;
78
+ @end
79
+ @implementation JoltSceneAppearanceSink
80
+ + (instancetype)target:(NSObject *)target kind:(JoltSceneAppearanceKind)kind {
81
+ NSParameterAssert(NSThread.isMainThread);
82
+ BOOL light = kind == JoltSceneIntensity || kind == JoltSceneLightColor;
83
+ if (![target isKindOfClass:light ? SCNLight.class : SCNMaterial.class])
84
+ [NSException raise:NSInvalidArgumentException format:@"Jolt SceneKit adapter requires %@", light ? @"SCNLight" : @"SCNMaterial"];
85
+ JoltSceneAppearanceSink *sink = [[self alloc] initWithTarget:target dimensions:(kind == JoltSceneIntensity || kind == JoltSceneTransparency) ? 1 : 4];
86
+ sink.kind = kind; appearanceSinks++;
87
+ if (!sink.alive) {
88
+ [sink dispose];
89
+ [NSException raise:NSInvalidArgumentException format:@"Jolt SceneKit adapter requires a valid scalar or solid UIColor/CGColor; textures, images, numbers, patterns and nil color contents are unsupported"];
90
+ }
91
+ return sink;
92
+ }
93
+ - (id)contents {
94
+ switch (self.kind) {
95
+ case JoltSceneLightColor: return [(SCNLight *)self.target color];
96
+ case JoltSceneDiffuse: return [(SCNMaterial *)self.target diffuse].contents;
97
+ case JoltSceneEmission: return [(SCNMaterial *)self.target emission].contents;
98
+ default: return nil;
99
+ }
100
+ }
101
+ - (BOOL)alive {
102
+ if (!self.target) return NO;
103
+ if (self.kind == JoltSceneIntensity || self.kind == JoltSceneTransparency) {
104
+ double value = self.kind == JoltSceneIntensity ? [(SCNLight *)self.target intensity] : [(SCNMaterial *)self.target transparency];
105
+ return isfinite(value) && value >= 0 && (self.kind != JoltSceneTransparency || value <= 1);
106
+ }
107
+ CGColorRef color = copySceneColor(self.contents);
108
+ BOOL valid = color != NULL;
109
+ if (color) CGColorRelease(color);
110
+ return valid;
111
+ }
112
+ - (NSArray<NSNumber *> *)read {
113
+ NSParameterAssert(NSThread.isMainThread);
114
+ if (!self.alive) [NSException raise:NSInvalidArgumentException format:@"Jolt SceneKit target or solid color contents are no longer valid"];
115
+ if (self.kind == JoltSceneIntensity) return @[@([(SCNLight *)self.target intensity])];
116
+ if (self.kind == JoltSceneTransparency) return @[@([(SCNMaterial *)self.target transparency])];
117
+ CGColorRef color = copySceneColor(self.contents);
118
+ const CGFloat *v = CGColorGetComponents(color);
119
+ CGFloat a = unitComponent(v[3]);
120
+ NSArray *result = @[@(unitComponent(v[0])*a), @(unitComponent(v[1])*a), @(unitComponent(v[2])*a), @(a)];
121
+ CGColorRelease(color);
122
+ return result;
123
+ }
124
+ - (void)write:(NSArray<NSNumber *> *)values {
125
+ NSParameterAssert(NSThread.isMainThread);
126
+ if (!self.alive) [NSException raise:NSInvalidArgumentException format:@"Jolt will not overwrite unsupported SceneKit contents"];
127
+ if (values.count != self.dimensions) [NSException raise:NSInvalidArgumentException format:@"Invalid SceneKit component count"];
128
+ for (NSNumber *value in values)
129
+ if (!isfinite(value.doubleValue)) [NSException raise:NSInvalidArgumentException format:@"Nonfinite SceneKit component"];
130
+ if (self.dimensions == 1 && (values[0].doubleValue < 0 || (self.kind == JoltSceneTransparency && values[0].doubleValue > 1)))
131
+ [NSException raise:NSInvalidArgumentException format:@"SceneKit scalar is outside its range"];
132
+ UIColor *color = nil;
133
+ if (self.dimensions == 4) {
134
+ double a = values[3].doubleValue;
135
+ if (a < 0 || a > 1) [NSException raise:NSInvalidArgumentException format:@"Invalid SceneKit alpha"];
136
+ for (NSUInteger i=0; i<3; i++)
137
+ if (values[i].doubleValue < 0 || values[i].doubleValue > a + 1e-9)
138
+ [NSException raise:NSInvalidArgumentException format:@"Invalid premultiplied SceneKit color"];
139
+ color = [UIColor colorWithRed:a ? unitComponent(values[0].doubleValue/a) : 0
140
+ green:a ? unitComponent(values[1].doubleValue/a) : 0
141
+ blue:a ? unitComponent(values[2].doubleValue/a) : 0 alpha:a];
142
+ }
143
+ [SCNTransaction begin]; [SCNTransaction setDisableActions:YES];
144
+ @try {
145
+ switch (self.kind) {
146
+ case JoltSceneIntensity: [(SCNLight *)self.target setIntensity:values[0].doubleValue]; break;
147
+ case JoltSceneTransparency: [(SCNMaterial *)self.target setTransparency:values[0].doubleValue]; break;
148
+ case JoltSceneLightColor: [(SCNLight *)self.target setColor:color]; break;
149
+ case JoltSceneDiffuse: [(SCNMaterial *)self.target diffuse].contents = color; break;
150
+ case JoltSceneEmission: [(SCNMaterial *)self.target emission].contents = color; break;
151
+ }
152
+ appearanceWrites++;
153
+ } @finally { [SCNTransaction commit]; }
154
+ }
155
+ - (void)dispose {
156
+ if (!self.released) { self.released = YES; appearanceSinks--; }
157
+ [super dispose];
158
+ }
159
+ - (void)dealloc { if (!self.released) appearanceSinks--; }
160
+ @end
161
+
50
162
  @implementation JoltReferenceProperties
163
+ + (JoltPropertySink *)sceneLightIntensity:(NSObject *)light { return [JoltSceneAppearanceSink target:light kind:JoltSceneIntensity]; }
164
+ + (JoltPropertySink *)sceneLightColor:(NSObject *)light { return [JoltSceneAppearanceSink target:light kind:JoltSceneLightColor]; }
165
+ + (JoltPropertySink *)sceneMaterialTransparency:(NSObject *)material { return [JoltSceneAppearanceSink target:material kind:JoltSceneTransparency]; }
166
+ + (JoltPropertySink *)sceneMaterialDiffuse:(NSObject *)material { return [JoltSceneAppearanceSink target:material kind:JoltSceneDiffuse]; }
167
+ + (JoltPropertySink *)sceneMaterialEmission:(NSObject *)material { return [JoltSceneAppearanceSink target:material kind:JoltSceneEmission]; }
168
+ + (NSUInteger)sceneAppearanceSinkCount { return appearanceSinks; }
169
+ + (NSUInteger)sceneAppearanceWriteCount { return appearanceWrites; }
51
170
  + (JoltPropertySink *)mapCoordinate:(NSObject *)annotation {
52
171
  NSParameterAssert([annotation conformsToProtocol:@protocol(MKAnnotation)] && [annotation respondsToSelector:@selector(setCoordinate:)]);
53
172
  return [[JoltMapCoordinateSink alloc] initWithTarget:annotation dimensions:2];