Task 686: .SSC File Format
Task 686: .SSC File Format
1. List of all the properties of the .SSC file format intrinsic to its file system
The .SSC (Solar System Catalog) file format is a plain-text format used by the Celestia space simulation software to define celestial objects (e.g., planets, moons, asteroids, spacecraft) and their attributes within a solar system. It is structured as a series of object definitions, each starting with an optional type prefix (e.g., "AltSurface", "Location", "ReferencePoint", "Modify", "Replace"), followed by a quoted name, a quoted path to the parent object(s), and a block of properties enclosed in curly braces {}. Properties are key-value pairs, with values being numbers, strings, booleans, or nested blocks. Comments start with #. Properties can be top-level (for the object) or nested within sub-structures like Atmosphere, EllipticalOrbit, Rings, etc.
Based on the specifications, here is a comprehensive list of all known properties intrinsic to the .SSC format (alphabetical order, including deprecated ones for completeness). These define the object's appearance, orbit, rotation, atmosphere, and other characteristics. Sub-properties are indented under their parent blocks.
- Albedo (float): Deprecated; geometric albedo (0.0-1.0) for brightness when rendered as a point.
- Atmosphere (block): Defines the object's atmosphere.
- Absorption (array [R G B]): Amount of RGB light absorbed.
- CloudHeight (float): Cloud altitude in km.
- CloudMap (string): Path to cloud texture file.
- CloudNormalMap (string): Path to cloud normal map for bump effects.
- CloudSpeed (float): Cloud rotation speed in km/h.
- Height (float): Atmosphere thickness in km.
- Lower (array [R G B]): Deprecated; color near surface.
- Mie (float): Mie scattering amount for haze.
- MieAsymmetry (float): Forward/backward scattering bias.
- MieScaleHeight (float): Vertical scale for Mie scattering.
- Rayleigh (array [R G B]): Rayleigh scattering amounts for RGB.
- Sky (array [R G B]): Deprecated; sky color from inside.
- Sunset (array [R G B]): Deprecated; sunset color.
- Upper (array [R G B]): Deprecated; color near top.
- Beginning (float or string): Julian date or "YYYY MM DD HH:MM:SS" when the object starts existing.
- BlendTexture (boolean): If true, blends texture with Color.
- BondAlbedo (float): Bond albedo (0.0-1.0) for temperature/reflectivity.
- BumpHeight (float): Scale of bump map relief (default 2.0).
- BumpMap (string): Path to bump map texture.
- Class (string): Object type (e.g., "planet", "moon", "dwarfplanet", "asteroid", "comet", "spacecraft", "surfacefeature", "component", "diffuse", "invisible", "minormoon").
- Clickable (boolean): If true, object can be selected by clicking (default true, except for "diffuse").
- Color (array [R G B]): Base color tint (0.0-1.0 per channel).
- CustomOrbit (string): Name of built-in orbit calculation.
- CustomRotation (string): Name of built-in rotation model (e.g., IAU model).
- EllipticalOrbit (block): Defines an elliptical orbit.
- ArgOfPericenter (float): Argument of pericenter in degrees.
- AscendingNode (float): Longitude of ascending node in degrees.
- Eccentricity (float): Orbit eccentricity (0.0-1.0).
- Epoch (float): Julian date for orbital elements.
- Inclination (float): Orbit inclination in degrees.
- MeanAnomaly (float): Mean anomaly at epoch in degrees.
- MeanLongitude (float): Mean longitude at epoch in degrees (alternative to MeanAnomaly).
- Period (float): Orbital period in days.
- SemiMajorAxis (float): Semi-major axis in AU (for stars) or km (for planets/moons).
- EllipticalRotation (block): Deprecated; similar to UniformRotation for elliptical rotation.
- Emissive (boolean): If true, object emits light (no shadows).
- EmissiveColor (array [R G B]): Color of emitted light.
- Ending (float or string): Julian date or "YYYY MM DD HH:MM:SS" when the object ceases existing.
- GeomAlbedo (float): Geometric albedo (0.0-1.0) for distant brightness.
- HazeColor (array [R G B]): Color of atmospheric haze.
- HazeDensity (float): Density of haze (0.0-1.0).
- InfoURL (string): URL for more information about the object.
- Mass (float): Mass in kg.
- Mesh (string): Path to 3D model file (e.g., .3ds, .cmod).
- NightTexture (string): Path to night-side texture.
- NormalMap (string): Path to normal map for surface bump effects.
- Oblateness (float): Flattening (0.0 for sphere, >0 for oblate).
- Obliquity (float): Deprecated; use rotation parameters.
- OverlayTexture (string): Path to overlay texture (e.g., labels).
- PrecessionRate (float): Precession rate in degrees per day.
- Radius (float): Radius in km; influences class if not specified.
- Rings (block): Defines ring system.
- Color (array [R G B]): Ring color.
- Inner (float): Inner radius in km.
- Outer (float): Outer radius in km.
- Texture (string): Path to ring texture.
- RotationOffset (float): Offset for rotation in degrees.
- RotationPeriod (float): Rotation period in hours (positive for direct, negative for retrograde).
- SpecularColor (array [R G B]): Color of specular highlights.
- SpecularPower (float): Shininess (higher = sharper highlights).
- SpecularTexture (string): Path to specular map.
- Texture (string): Path to main surface texture.
- UniformRotation (block): Defines uniform rotation.
- AscendingNode (float): Longitude of ascending node in degrees.
- Inclination (float): Axial tilt in degrees.
- MeridianAngle (float): Rotation at epoch in degrees.
- Period (float): Rotation period in hours.
- Visible (boolean): If false, object is not rendered (default true).
Additional prefixes for special definitions: AltSurface (alternative textures), Location (surface labels with coords), ReferencePoint (orbital points), Modify/Replace (edit existing objects).
2. Two direct download links for files of format .SSC
- https://raw.githubusercontent.com/CelestiaProject/Celestia/master/data/solarsys.ssc (Defines major Solar System bodies like planets and moons.)
- https://raw.githubusercontent.com/CelestiaProject/Celestia/master/extras-standard/iss/iss.ssc (Defines the International Space Station as a spacecraft.)
3. Ghost blog embedded html javascript for drag n drop .SSC file dump
Here's a self-contained HTML page with embedded JavaScript. It creates a drop zone; when a .SSC file is dropped, it reads the file as text, parses it into objects and their properties (handling nested blocks and comments), and dumps them to the screen in a readable format (e.g., JSON-like structure).
4. Python class for .SSC file handling
import json
import re
class SSCFile:
def __init__(self, filename=None):
self.objects = []
if filename:
self.read(filename)
def read(self, filename):
with open(filename, 'r') as f:
text = f.read()
lines = [line.strip() for line in text.split('\n') if line.strip() and not line.strip().startswith('#')]
current_object = None
current_block = None
stack = []
i = 0
while i < len(lines):
line = lines[i]
if '{' in line:
parts = re.split(r'\s+', line.split('{')[0].strip())
parts = [p.strip('"') for p in parts]
if len(parts) >= 2:
current_object = {'type': parts[0] if parts[0] in ['AltSurface', 'Location', 'ReferencePoint', 'Modify', 'Replace'] else '', 'name': parts[0] if not current_object else parts[1], 'path': parts[-1], 'properties': {}}
self.objects.append(current_object)
current_block = current_object['properties']
stack = [current_block]
else:
key = parts[0]
current_block[key] = {}
stack.append(current_block[key])
current_block = current_block[key]
elif line == '}':
stack.pop()
current_block = stack[-1] if stack else None
else:
match = re.match(r'(\w+)\s+(.*)', line)
if match:
key, value = match.groups()
value = value.strip()
if value.startswith('['):
current_block[key] = [float(v) for v in re.findall(r'[\d.]+', value)]
elif re.match(r'^\d+(\.\d+)?$', value):
current_block[key] = float(value)
elif value.lower() in ['true', 'false']:
current_block[key] = value.lower() == 'true'
elif re.match(r'^\d{4}\s+\d{1,2}\s+\d{1,2}', value):
current_block[key] = value # date string
else:
current_block[key] = value.strip('"')
i += 1
def write(self, filename):
with open(filename, 'w') as f:
for obj in self.objects:
type_prefix = f'{obj["type"]} ' if obj["type"] else ''
f.write(f'{type_prefix}"{obj["name"]}" "{obj["path"]}" {{\n')
self._write_properties(f, obj['properties'], indent=1)
f.write('}\n\n')
def _write_properties(self, f, props, indent):
for key, value in props.items():
ind = ' ' * indent
if isinstance(value, dict):
f.write(f'{ind}{key} {{\n')
self._write_properties(f, value, indent + 1)
f.write(f'{ind}}}\n')
elif isinstance(value, list):
f.write(f'{ind}{key} [ {" ".join(map(str, value))} ]\n')
elif isinstance(value, bool):
f.write(f'{ind}{key} {str(value).lower()}\n')
else:
if isinstance(value, str) and ' ' in value and not value.startswith('"'):
value = f'"{value}"'
f.write(f'{ind}{key} {value}\n')
def print_properties(self):
print(json.dumps(self.objects, indent=2))
# Example usage:
# ssc = SSCFile('example.ssc')
# ssc.print_properties()
# ssc.write('output.ssc')
5. Java class for .SSC file handling
import java.io.*;
import java.util.*;
import java.util.regex.*;
public class SSCFile {
private List<Map<String, Object>> objects = new ArrayList<>();
public SSCFile(String filename) throws IOException {
if (filename != null) {
read(filename);
}
}
public void read(String filename) throws IOException {
BufferedReader reader = new BufferedReader(new FileReader(filename));
String line;
Stack<Map<String, Object>> stack = new Stack<>();
Map<String, Object> currentObject = null;
Map<String, Object> currentBlock = null;
while ((line = reader.readLine()) != null) {
line = line.trim();
if (line.isEmpty() || line.startsWith("#")) continue;
if (line.contains("{")) {
String[] parts = line.split("\\{")[0].trim().split("\\s+");
for (int i = 0; i < parts.length; i++) parts[i] = parts[i].replace("\"", "");
if (parts.length >= 2) {
currentObject = new HashMap<>();
String type = "";
String name = parts[0];
if (Arrays.asList("AltSurface", "Location", "ReferencePoint", "Modify", "Replace").contains(parts[0])) {
type = parts[0];
name = parts[1];
}
currentObject.put("type", type);
currentObject.put("name", name);
currentObject.put("path", parts[parts.length - 1]);
currentObject.put("properties", new HashMap<String, Object>());
objects.add(currentObject);
currentBlock = (Map<String, Object>) currentObject.get("properties");
stack = new Stack<>();
stack.push(currentBlock);
} else {
String key = parts[0];
Map<String, Object> newBlock = new HashMap<>();
currentBlock.put(key, newBlock);
stack.push(newBlock);
currentBlock = newBlock;
}
} else if (line.equals("}")) {
stack.pop();
currentBlock = stack.isEmpty() ? null : stack.peek();
} else if (currentBlock != null) {
Matcher matcher = Pattern.compile("(\\w+)\\s+(.*)").matcher(line);
if (matcher.matches()) {
String key = matcher.group(1);
String value = matcher.group(2).trim();
if (value.startsWith("[")) {
List<Float> list = new ArrayList<>();
Matcher numMatcher = Pattern.compile("[\\d.]+").matcher(value);
while (numMatcher.find()) list.add(Float.parseFloat(numMatcher.group()));
currentBlock.put(key, list);
} else {
try {
currentBlock.put(key, Float.parseFloat(value));
} catch (NumberFormatException e) {
if (value.toLowerCase().equals("true") || value.toLowerCase().equals("false")) {
currentBlock.put(key, Boolean.parseBoolean(value));
} else {
currentBlock.put(key, value.replace("\"", ""));
}
}
}
}
}
}
reader.close();
}
public void write(String filename) throws IOException {
BufferedWriter writer = new BufferedWriter(new FileWriter(filename));
for (Map<String, Object> obj : objects) {
String typePrefix = obj.get("type").toString().isEmpty() ? "" : obj.get("type") + " ";
writer.write(typePrefix + "\"" + obj.get("name") + "\" \"" + obj.get("path") + "\" {\n");
writeProperties(writer, (Map<String, Object>) obj.get("properties"), 1);
writer.write("}\n\n");
}
writer.close();
}
private void writeProperties(BufferedWriter writer, Map<String, Object> props, int indent) throws IOException {
String indStr = " ".repeat(indent);
for (Map.Entry<String, Object> entry : props.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof Map) {
writer.write(indStr + key + " {\n");
writeProperties(writer, (Map<String, Object>) value, indent + 1);
writer.write(indStr + "}\n");
} else if (value instanceof List) {
writer.write(indStr + key + " [ " + ((List<?>) value).stream().map(Object::toString).reduce((a, b) -> a + " " + b).orElse("") + " ]\n");
} else if (value instanceof Boolean) {
writer.write(indStr + key + " " + value.toString().toLowerCase() + "\n");
} else {
String valStr = value.toString();
if (valStr.contains(" ") && !valStr.startsWith("\"")) valStr = "\"" + valStr + "\"";
writer.write(indStr + key + " " + valStr + "\n");
}
}
}
public void printProperties() {
System.out.println(objects); // Simple print; use Gson for pretty JSON if needed
}
// Example usage:
// public static void main(String[] args) throws IOException {
// SSCFile ssc = new SSCFile("example.ssc");
// ssc.printProperties();
// ssc.write("output.ssc");
// }
}
6. Javascript class for .SSC file handling
class SSCFile {
constructor(filename = null) {
this.objects = [];
if (filename) {
this.read(filename);
}
}
async read(filename) {
const response = await fetch(filename);
const text = await response.text();
const lines = text.split('\n').map(l => l.trim()).filter(l => l && !l.startsWith('#'));
let currentObject = null;
let currentBlock = null;
let stack = [];
for (let line of lines) {
if (line.includes('{')) {
const parts = line.split('{')[0].trim().split(/\s+/).map(p => p.replace(/"/g, ''));
if (parts.length >= 2) {
const type = ['AltSurface', 'Location', 'ReferencePoint', 'Modify', 'Replace'].includes(parts[0]) ? parts[0] : '';
const name = type ? parts[1] : parts[0];
currentObject = { type, name, path: parts[parts.length - 1], properties: {} };
this.objects.push(currentObject);
currentBlock = currentObject.properties;
stack = [currentBlock];
} else {
const key = parts[0];
currentBlock[key] = {};
stack.push(currentBlock[key]);
currentBlock = currentBlock[key];
}
} else if (line === '}') {
stack.pop();
currentBlock = stack.length ? stack[stack.length - 1] : null;
} else if (currentBlock) {
const [key, ...valueParts] = line.split(/\s+/);
let value = valueParts.join(' ').trim();
if (value.startsWith('[')) {
currentBlock[key] = value.replace(/[\[\]]/g, '').split(/\s+/).map(parseFloat);
} else if (!isNaN(parseFloat(value))) {
currentBlock[key] = parseFloat(value);
} else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
currentBlock[key] = value.toLowerCase() === 'true';
} else {
currentBlock[key] = value.replace(/"/g, '');
}
}
}
}
write() {
let output = '';
for (let obj of this.objects) {
const typePrefix = obj.type ? `${obj.type} ` : '';
output += `${typePrefix}"${obj.name}" "${obj.path}" {\n`;
output += this._writeProperties(obj.properties, 1);
output += '}\n\n';
}
return output;
}
_writeProperties(props, indent) {
let str = '';
const ind = ' '.repeat(indent);
for (let [key, value] of Object.entries(props)) {
if (typeof value === 'object' && !Array.isArray(value)) {
str += `${ind}${key} {\n`;
str += this._writeProperties(value, indent + 1);
str += `${ind}}\n`;
} else if (Array.isArray(value)) {
str += `${ind}${key} [ ${value.join(' ')} ]\n`;
} else if (typeof value === 'boolean') {
str += `${ind}${key} ${value ? 'true' : 'false'}\n`;
} else {
let valStr = value.toString();
if (valStr.includes(' ') && !valStr.startsWith('"')) valStr = `"${valStr}"`;
str += `${ind}${key} ${valStr}\n`;
}
}
return str;
}
printProperties() {
console.log(JSON.stringify(this.objects, null, 2));
}
}
// Example usage:
// const ssc = new SSCFile();
// await ssc.read('example.ssc');
// ssc.printProperties();
// console.log(ssc.write()); // Outputs to console; save to file as needed
7. C class (using C++ for class support) for .SSC file handling
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <map>
#include <regex>
#include <string>
class SSCFile {
private:
std::vector<std::map<std::string, std::any>> objects; // Requires C++17 for std::any
public:
SSCFile(const std::string& filename = "") {
if (!filename.empty()) {
read(filename);
}
}
void read(const std::string& filename) {
std::ifstream file(filename);
std::string line;
std::vector<std::map<std::string, std::any>*> stack;
std::map<std::string, std::any>* currentObject = nullptr;
std::map<std::string, std::any>* currentBlock = nullptr;
while (std::getline(file, line)) {
line = std::regex_replace(line, std::regex("^\\s+|\\s+$"), "");
if (line.empty() || line[0] == '#') continue;
if (line.find('{') != std::string::npos) {
std::string header = line.substr(0, line.find('{'));
header = std::regex_replace(header, std::regex("^\\s+|\\s+$"), "");
std::vector<std::string> parts;
std::stringstream ss(header);
std::string part;
while (ss >> part) {
part = std::regex_replace(part, std::regex("\""), "");
parts.push_back(part);
}
if (parts.size() >= 2) {
std::map<std::string, std::any> newObj;
std::string type = "";
std::string name = parts[0];
std::vector<std::string> types = {"AltSurface", "Location", "ReferencePoint", "Modify", "Replace"};
if (std::find(types.begin(), types.end(), parts[0]) != types.end()) {
type = parts[0];
name = parts[1];
}
newObj["type"] = type;
newObj["name"] = name;
newObj["path"] = parts.back();
newObj["properties"] = std::map<std::string, std::any>{};
objects.push_back(newObj);
currentObject = &objects.back();
currentBlock = &std::any_cast<std::map<std::string, std::any>&>((*currentObject)["properties"]);
stack.clear();
stack.push_back(currentBlock);
} else {
std::string key = parts[0];
(*currentBlock)[key] = std::map<std::string, std::any>{};
stack.push_back(&std::any_cast<std::map<std::string, std::any>&>((*currentBlock)[key]));
currentBlock = stack.back();
}
} else if (line == "}") {
stack.pop_back();
currentBlock = stack.empty() ? nullptr : stack.back();
} else if (currentBlock) {
std::smatch match;
if (std::regex_match(line, match, std::regex("(\\w+)\\s+(.*)"))) {
std::string key = match[1];
std::string value = match[2];
value = std::regex_replace(value, std::regex("^\\s+|\\s+$"), "");
if (value[0] == '[') {
std::vector<float> list;
std::smatch numMatch;
std::string nums = value.substr(1, value.size() - 2);
std::regex numRegex("[\\d.]+");
auto iter = std::sregex_iterator(nums.begin(), nums.end(), numRegex);
for (auto i = iter; i != std::sregex_iterator(); ++i) {
list.push_back(std::stof((*i).str()));
}
(*currentBlock)[key] = list;
} else {
try {
(*currentBlock)[key] = std::stof(value);
} catch (...) {
if (value == "true" || value == "false") {
(*currentBlock)[key] = (value == "true");
} else {
value = std::regex_replace(value, std::regex("\""), "");
(*currentBlock)[key] = value;
}
}
}
}
}
}
}
void write(const std::string& filename) {
std::ofstream file(filename);
for (auto& obj : objects) {
std::string typePrefix = std::any_cast<std::string>(obj["type"]).empty() ? "" : std::any_cast<std::string>(obj["type"]) + " ";
file << typePrefix << "\"" << std::any_cast<std::string>(obj["name"]) << "\" \"" << std::any_cast<std::string>(obj["path"]) << "\" {\n";
writeProperties(file, std::any_cast<std::map<std::string, std::any>>(obj["properties"]), 1);
file << "}\n\n";
}
}
private:
void writeProperties(std::ofstream& file, const std::map<std::string, std::any>& props, int indent) {
std::string ind(indent * 2, ' ');
for (const auto& [key, value] : props) {
if (value.type() == typeid(std::map<std::string, std::any>)) {
file << ind << key << " {\n";
writeProperties(file, std::any_cast<std::map<std::string, std::any>>(value), indent + 1);
file << ind << "}\n";
} else if (value.type() == typeid(std::vector<float>)) {
file << ind << key << " [";
for (auto v : std::any_cast<std::vector<float>>(value)) file << " " << v;
file << " ]\n";
} else if (value.type() == typeid(bool)) {
file << ind << key << " " << (std::any_cast<bool>(value) ? "true" : "false") << "\n";
} else {
std::string valStr = std::any_cast<std::string>(value); // Assume string for non-numeric
if (valStr.find(' ') != std::string::npos && valStr[0] != '"') valStr = "\"" + valStr + "\"";
file << ind << key << " " << valStr << "\n";
}
}
}
public:
void printProperties() {
// Simple console output; implement JSON-like if needed
for (const auto& obj : objects) {
std::cout << "{ \"type\": \"" << std::any_cast<std::string>(obj.at("type")) << "\", \"name\": \"" << std::any_cast<std::string>(obj.at("name")) << "\", \"path\": \"" << std::any_cast<std::string>(obj.at("path")) << "\", \"properties\": { ... } }" << std::endl;
}
}
};
// Example usage:
// int main() {
// SSCFile ssc("example.ssc");
// ssc.printProperties();
// ssc.write("output.ssc");
// return 0;
// }