G3dLightingCmdsImpl.mesa
Copyright Ó 1985, 1992 by Xerox Corporation. All rights reserved.
Glassner, July 24, 1989 12:29:13 pm PDT
Bloomenthal, October 20, 1992 3:22 pm PDT
DIRECTORY Ascii, Args, Buttons, Commander, Controls, Convert, CtBasic, CtFile, CtMap, Draw2d, FileNames, FS, G2dBasic, G3dBasic, G3dFunction, G3dIO, G3dNormalCoding, G3dTool, G3dVector, Icons, Imager, ImagerColor, ImagerFont, ImagerSample, IO, MessageWindow, Process, Random, Real, RealFns, Rope, TiogaAccess, VFonts, ViewerClasses, ViewerOps, ViewerSpecs;
G3dLightingCmdsImpl: CEDAR PROGRAM
IMPORTS Args, Buttons, Controls, Convert, CtBasic, CtFile, CtMap, FileNames, FS, G3dIO, G3dNormalCoding, G3dTool, G3dVector, ImagerColor, ImagerSample, Icons, IO, MessageWindow, Process, Random, Real, RealFns, Rope, TiogaAccess, VFonts, ViewerOps, ViewerSpecs
~ BEGIN
Imported Types
CommandProc:   TYPE ~ Commander.CommandProc;
ButtonList:    TYPE ~ Controls.ButtonList;
Button:     TYPE ~ Controls.Button;
ClickProc:    TYPE ~ Controls.ClickProc;
Control:     TYPE ~ Controls.Control;
ControlList:    TYPE ~ Controls.ControlList;
DestroyProc:    TYPE ~ Controls.DestroyProc;
Mouse:     TYPE ~ Controls.Mouse;
MouseProc:    TYPE ~ Controls.MouseProc;
OuterData:    TYPE ~ Controls.OuterData;
Typescript:    TYPE ~ Controls.Typescript;
Viewer:     TYPE ~ Controls.Viewer;
SampleMap:    TYPE ~ CtBasic.SampleMap;
SampleMaps:    TYPE ~ CtBasic.SampleMaps;
Cmap:      TYPE ~ CtMap.Cmap;
SRGB:      TYPE ~ CtMap.RGB;    -- scaled RGB: Cardinals [0-255] (rgb)
DrawProc:    TYPE ~ G3dTool.DrawProc;
Pair:      TYPE ~ G2dBasic.Pair;
Triple:     TYPE ~ G3dBasic.Triple;
URGB:      TYPE ~ ImagerColor.RGB;  -- unscaled RGB: Reals [0-1] (RGB)
Font:      TYPE ~ ImagerFont.Font; 
Box:      TYPE ~ ImagerSample.Box;
STREAM:     TYPE ~ IO.STREAM;
ROPE:      TYPE ~ Rope.ROPE;
Writer:     TYPE ~ TiogaAccess.Writer;
Local Types
Color:      TYPE ~ RECORD [rgb: URGB];
ColorArray:    TYPE ~ ARRAY [0..255] OF Color;
RealArray:    TYPE ~ ARRAY [0..255] OF REAL;
LightAccelerators:  TYPE ~ RECORD [
cached, cachep:     REF RealArray ¬ NIL,
cacheD, cacheS:     REF ColorArray ¬ NIL];
Light:      TYPE ~ RECORD [
name:        ROPE ¬ NIL,
direction:       Triple ¬ [0.0, 0.0, 1.0],
spread:       REAL ¬ 1.0,
color:        Color ¬ [rgb: [1.0, 1.0, 1.0]],
accelerators:      REF LightAccelerators ¬ NIL];
MaterialProps:   TYPE ~ RECORD [
kd:        REAL ¬ 1.0,
ks:         REAL ¬ 1.0,
kr:         REAL ¬ 1.0,
ke:        REAL ¬ 20.0,
kt:         REAL ¬ 1.0,
km:        REAL ¬ 1.0,
kc:        REAL ¬ 1.0];
MaterialAccelerators: TYPE ~ RECORD [cacheMw: Color];
Material:     TYPE ~ RECORD [
name:        ROPE ¬ NIL,
materialProps:     MaterialProps ¬ [],
color:        Color ¬ [rgb: [1.0, 1.0, 1.0]],
accelerators:      REF MaterialAccelerators ¬ NIL];
MaterialSequence:   TYPE ~ REF MaterialSequenceRep;
MaterialSequenceRep:  TYPE ~ RECORD [
length:       CARDINAL ¬ 0,
element:       SEQUENCE maxLength: CARDINAL OF Material];
LightSequence:   TYPE ~ REF LightSequenceRep;
LightSequenceRep:  TYPE ~ RECORD [
length:       CARDINAL ¬ 0,
element:       SEQUENCE maxLength: CARDINAL OF Light];
ToolState:     TYPE ~ REF ToolStateRep;
ToolStateRep:    TYPE ~ RECORD [
-- debug flags
redrawSphere:     BOOLEAN ¬ FALSE,
use24bits:       BOOLEAN ¬ FALSE,
radius:       INTEGER ¬ 203,
-- image file
imageFileName:     ROPE ¬ NIL,
-- normal flags
nextPressSpecial:     BOOLEAN ¬ TRUE,
pointDirection:     BOOLEAN ¬ TRUE,
pointHighlight:     BOOLEAN ¬ FALSE,
placeInvertZ:      BOOLEAN ¬ FALSE,
lightFocus:      BOOLEAN ¬ TRUE,
rs:         Random.RandomStream,
maps:        SampleMaps ¬ NIL];
LightingTool:   TYPE ~ REF LightingToolRep;
LightingToolRep:  TYPE ~ RECORD [
-- light source data
lights:        LightSequence,
activeLight:      INT ¬ 0,
-- material data
materials:       MaterialSequence,
activeMaterial:     INT ¬ 0,
-- lighting controls
thetaControl:      Control ¬ NIL,
psiControl:      Control ¬ NIL,
spreadControl:     Control ¬ NIL,
falloffControl:     Control ¬ NIL,
lightColorSlider1:    Control ¬ NIL,
lightColorSlider2:    Control ¬ NIL,
lightColorSlider3:    Control ¬ NIL,
lightSpectrum:     Control ¬ NIL,
-- surface controls
kdControl:      Control ¬ NIL,
ksControl:      Control ¬ NIL,
krControl:      Control ¬ NIL,
keControl:      Control ¬ NIL,
ktControl:       Control ¬ NIL,
kmControl:      Control ¬ NIL,
kcControl:      Control ¬ NIL,
materialColorSlider1:    Control ¬ NIL,
materialColorSlider2:    Control ¬ NIL,
materialColorSlider3:    Control ¬ NIL,
materialSpectrum:    Control ¬ NIL,
special buttons
activeLightButton:    Button,
activeMaterialButton:   Button,
-- tool state
state:        ToolState,
-- viewer stuff
outer:        Viewer ¬ NIL,  -- the parent viewer
outerData:      OuterData ¬ NIL, -- controls information
ts:         Typescript ¬ NIL, -- for user io
lightColorPatch:     Box,     -- place for color sample
materialColorPatch:    Box,     -- place for color sample
myButtons:      ButtonList ¬ NIL, -- my buttons
graphics:       Viewer ¬ NIL];  -- for drawing the sphere
Constants
fgColor:   INT = 0;
bgColor:   INT = 255;
lightColor:  INT = 1;
materialColor: INT = 2;
pi:     REAL = 3.1415926535;
twoPi:    REAL = 6.283185;
piOver2:   REAL = 1.570796;
piOver4:   REAL = 0.7853982;
program:   ROPE ¬ "3dLighting Designer";
version:   ROPE ¬ "v1.0";
Errors
NotANormal: ERROR = CODE;
Commands
LightingCmd: CommandProc ~ {
state: ColorDisplayManager.State ¬ ColorDisplayManager.NextState[];
fileName: ROPE ¬ NIL;
fileNameA: Args.Arg;
[fileNameA] ¬ Args.ArgsGet[cmd, "%[s"];
IF fileNameA.ok THEN fileName ¬ fileNameA.rope;
IF state.level # viewers OR state.type # $FullColor
THEN IO.PutF[cmd.out, "Before running 3dlighting, please viewer-enable color display in 24 bpp mode\n"]
ELSE CreateMyTool[fileName];
};
Tool Creation / Initialization
CreateMyTool: PROC [fileName: ROPE] ~ {
lt: LightingTool ¬ NEW[LightingToolRep ¬ []];
CtBasic.CloseColorViewers[];
lt.state ¬ NEW[ToolStateRep ¬ []];
lt.state.imageFileName ¬ fileName;
MakeControls[lt];
lt.outer ¬ Controls.OuterViewer[
name: "3dLighting Designer",
column: color,
buttons: lt.myButtons,
graphicsHeight: 300,      -- height of color display's sphere
mouseProc: LightingMouse,    -- select a point on the sphere
drawProc: LightingDraw,     -- draw the sphere
destroyProc: LightingDestroy,
typescriptHeight: 36,
clientData: lt,
noOpen: TRUE].parent;
InitializeMaterials[lt];
InitializeLights[lt];
lt.lightColorPatch ¬ [[90, 425], [180, 460]];
lt.materialColorPatch ¬ [[280, 425], [365, 460]];
lt.outer.icon ¬ lightingIcon;
DrawInitialControls[lt];
lt.state.redrawSphere ¬ TRUE;
lt.state.use24bits ¬ FALSE;
lt.outerData ¬ NARROW[lt.outer.data];
lt.graphics ¬ lt.outerData.graphics;
lt.ts ¬ lt.outerData.typescript;
ViewerOps.OpenIcon[lt.outer];
lt.state.rs ¬ Random.Create[range: 500, seed: 1989];
UpdateActiveLight[lt];
UpdateActiveMaterial[lt];
UpdateLightColorSliders[lt];
UpdateMaterialColorSliders[lt];
BuildColormap[lt];
};
MakeControls: PROC [lt: LightingTool] ~ {
myFont: Font ← VFonts.EstablishFont[family: "Tioga", size: 4];
myFont: Font ¬ VFonts.DefaultFont[];
lt.lightColorSlider1 ¬ Controls.NewControl[name: "H:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: LightColorSliders, x: 520, y: 375, w: 105, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.lightColorSlider2 ¬ Controls.NewControl[name: "S:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: LightColorSliders, x: 520, y: 360, w: 105, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.lightColorSlider3 ¬ Controls.NewControl[name: "L:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: LightColorSliders, x: 520, y: 345, w: 105, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.lightSpectrum ¬ Controls.NewControl[type: function, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: LightSpectrumFunction, x: 520, y: 305, w: 105, h: 35, textLocation: [left, down], font: myFont];
lt.psiControl ¬ Controls.NewControl[name: "Psi", type: vSlider, clientData: lt, min: -pi/2.0, max: pi/2.0, init: 0.0, proc: PsiControl, x: 540, y: 225, w: 30, h: 45, font: myFont];
lt.thetaControl ¬ Controls.NewControl[name: "Theta", type: dial, clientData: lt, min: pi, max: -pi, init: 0.0, proc: ThetaControl, x: 585, y: 225, w: 45, h: 60, font: myFont];
lt.spreadControl ¬ Controls.NewControl[name: "S:", type: hSlider, clientData: lt, min: 0., max: 80.0, init: 1.0, proc: SpreadControl, x: 475, y: 225, w: 50, h: 13, textLocation: [left, down, TRUE], font: myFont, taper: exp];
lt.materialColorSlider1 ¬ Controls.NewControl[name: "H:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialColorSliders, x: 520, y: 190, w: 105, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.materialColorSlider2 ¬ Controls.NewControl[name: "S:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialColorSliders, x: 520, y: 175, w: 105, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.materialColorSlider3 ¬ Controls.NewControl[name: "L:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialColorSliders, x: 520, y: 160, w: 105, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.materialSpectrum ¬ Controls.NewControl[ type: function, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialSpectrumFunction, x: 520, y: 120, w: 105, h: 35, textLocation: [left, down], font: myFont];
lt.kdControl ¬ Controls.NewControl[name: "kd:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialControls, x: 545, y: 100, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.ksControl ¬ Controls.NewControl[name: "ks:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialControls, x: 545, y: 85, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.keControl ¬ Controls.NewControl[name: "ke:", type: hSlider, clientData: lt, min: 0.0, max: 80.0, init: 20.0, proc: MaterialControls, x: 545, y: 70, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont, taper: exp];
lt.kmControl ¬ Controls.NewControl[name: "km:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialControls, x: 545, y: 55, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.kcControl ← Controls.NewControl[name: "kc:", type: hSlider, clientData: lt, min: 0.0, max: 80.0, init: 1.0, proc: MaterialControls, x: 545, y: 40, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont, taper: exp];
lt.krControl ¬ Controls.NewControl[name: "kr:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialControls, x: 545, y: 25, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.ktControl ¬ Controls.NewControl[name: "kt:", type: hSlider, clientData: lt, min: 0.0, max: 1.0, init: 1.0, proc: MaterialControls, x: 545, y: 10, w: 85, h: 13, textLocation: [left, down, TRUE], font: myFont];
lt.myButtons ¬ CONS[Controls.ClickButton["Debug", DebugOps, lt], lt.myButtons];
lt.myButtons ¬ CONS[Controls.ClickButton["Sphere", SphereOps, lt], lt.myButtons];
lt.myButtons ¬ CONS[Controls.ClickButton["IO", IOOps, lt], lt.myButtons];
lt.myButtons ¬ CONS[Controls.ClickButton["Material Ops", MaterialOps, lt], lt.myButtons];
lt.myButtons ¬ CONS[Controls.ClickButton["Lighting Ops", LightingOps, lt], lt.myButtons];
lt.myButtons ¬ CONS[Controls.ClickButton["Place Light", PlacingOps, lt], lt.myButtons];
lt.activeLightButton ¬ Controls.ClickButton[name: "Default", proc: SelectLightOps, clientData: lt, x: 435, y: 255];
lt.activeMaterialButton ¬ Controls.ClickButton[name: "Default", proc: SelectMaterialOps, clientData: lt, x: 435, y: 75];
lt.myButtons ← CONS[lt.activeLightButton, lt.myButtons];
lt.myButtons ← CONS[lt.activeMaterialButton, lt.myButtons];
};
Control Draw/Erase Ops
DrawInitialControls: PROC [lt: LightingTool] ~ {
DrawLightingControls[lt];
DrawMaterialControls[lt];
};
DrawLightingControls: PROC [lt: LightingTool] ~ {
DrawControl[lt, lt.lightColorSlider1];
DrawControl[lt, lt.lightColorSlider2];
DrawControl[lt, lt.lightColorSlider3];
DrawControl[lt, lt.lightSpectrum];
DrawControl[lt, lt.psiControl];
DrawControl[lt, lt.thetaControl];
DrawControl[lt, lt.spreadControl];
ViewerOps.PaintViewer[lt.outer, client];
};
EraseLightingControls: PROC [lt: LightingTool] ~ {
EraseControl[lt, lt.lightColorSlider1];
EraseControl[lt, lt.lightColorSlider2];
EraseControl[lt, lt.lightColorSlider3];
EraseControl[lt, lt.lightSpectrum];
EraseControl[lt, lt.psiControl];
EraseControl[lt, lt.thetaControl];
EraseControl[lt, lt.spreadControl];
ViewerOps.PaintViewer[lt.outer, client];
};
DrawMaterialControls: PROC [lt: LightingTool] ~ {
DrawControl[lt, lt.materialColorSlider1];
DrawControl[lt, lt.materialColorSlider2];
DrawControl[lt, lt.materialColorSlider3];
DrawControl[lt, lt.materialSpectrum];
DrawControl[lt, lt.kdControl];
DrawControl[lt, lt.ksControl];
DrawControl[lt, lt.krControl];
DrawControl[lt, lt.keControl];
DrawControl[lt, lt.ktControl];
DrawControl[lt, lt.kmControl];
DrawControl[lt, lt.kcControl];
ViewerOps.PaintViewer[lt.outer, client];
};
EraseMaterialControls: PROC [lt: LightingTool] ~ {
EraseControl[lt, lt.materialColorSlider1];
EraseControl[lt, lt.materialColorSlider2];
EraseControl[lt, lt.materialColorSlider3];
EraseControl[lt, lt.materialSpectrum];
EraseControl[lt, lt.kdControl];
EraseControl[lt, lt.ksControl];
EraseControl[lt, lt.krControl];
EraseControl[lt, lt.keControl];
EraseControl[lt, lt.ktControl];
EraseControl[lt, lt.kmControl];
EraseControl[lt, lt.kcControl];
ViewerOps.PaintViewer[lt.outer, client];
};
DrawControl: PROC [lt: LightingTool, control: Control] ~ {
Controls.ControlViewer[lt.outer, control, NIL, lt.outerData];
};
EraseControl: PROC [lt: LightingTool, control: Control] ~ {
ViewerOps.DestroyViewer[control.viewer, FALSE];
ViewerOps.DestroyViewer[control.title, FALSE];
ViewerOps.DestroyViewer[control.status, FALSE];
};
Button Ops
Redraw: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
ViewerOps.PaintViewer[lt.graphics, client, FALSE, $Redraw];
};
LightingOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ Controls.PopUpRequest[["Light Ops"], LIST[
-- 1 -- ["Add Light", "Add a new light source"],
-- 2 -- ["Delete Light", "Delete light source"],
-- 3 -- ["Activate Light", "Make a light source active"]
]];
SELECT choice FROM
1 =>  AddLight[lt];
2 =>  DeleteLight[lt];
3 =>  ActivateLight[lt];
ENDCASE;
SELECT choice FROM
1 =>  LightChanged[lt];
2 =>  LightChanged[lt];
ENDCASE;
};
MaterialOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ Controls.PopUpRequest[["Material Ops"], LIST[
-- 1 -- ["Add Material", "Add a new material"],
-- 2 -- ["Delete Material", "Delete material"],
-- 3 -- ["Activate Material", "Make a material active"]]];
SELECT choice FROM
1 =>  AddMaterial[lt];
2 =>  DeleteMaterial[lt];
3 =>  ActivateMaterial[lt];
ENDCASE;
SELECT choice FROM
1 =>  MaterialChanged[lt];
2 =>  MaterialChanged[lt];
3 =>  MaterialChanged[lt];
ENDCASE;
};
PlacingOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ Controls.PopUpRequest[["Place Active Light"], LIST[
-- 1 -- ["Place Direction", "Point to light direction"],
-- 2 -- ["Place Highlight", "Point to highlight direction"],
IF lt.state.placeInvertZ
THEN -- 3 -- ["Point Away", "Point light away from you"]
ELSE -- 3 -- ["Point Towards", "Point light towards you"]
]];
SELECT choice FROM
1 =>  DirectionPoint[lt];
2 =>  HighlightPoint[lt];
3 =>  lt.state.placeInvertZ ¬ NOT lt.state.placeInvertZ;
ENDCASE;
SELECT choice FROM
1 =>  LightChanged[lt];
2 =>  LightChanged[lt];
3 =>  LightChanged[lt];
ENDCASE;
};
SphereOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ Controls.PopUpRequest[["HiRes Sphere"], LIST[
-- 1 -- ["Enter radius", "Type in desired radius"],
-- 2 -- ["Full size", "Full size"],
-- 3 -- ["150", "radius 150 pixels"],
-- 4 -- ["100", "radius 100 pixels"],
-- 5 -- ["50", "radius 50 pixels"],
-- 6 -- ["25", "radius 25 pixels"]
]];
IF choice > 0 THEN {
lt.state.redrawSphere ¬ lt.state.use24bits ¬ TRUE;
SELECT choice FROM
1 =>  {
s: IO.STREAM ¬ IO.RIS[Controls.TypescriptRead[lt.ts,"enter radius: "]];
lt.state.radius ¬ Convert.IntFromRope[IO.GetTokenRope[s ! IO.EndOfStream => GOTO Eof].token];
IF lt.state.radius <= 1 THEN lt.state.radius ¬ 2;
IF lt.state.radius > 200 THEN lt.state.radius ¬ 200;
};
2 =>  lt.state.radius ¬ 203;
3 =>  lt.state.radius ¬ 150;
4 =>  lt.state.radius ¬ 100;
5 =>  lt.state.radius ¬ 50;
6 =>  lt.state.radius ¬ 25;
ENDCASE;
CtMap.Mono[];
ViewerOps.PaintViewer[lt.outer, client];
lt.state.use24bits ¬ FALSE;
lt.state.redrawSphere ¬ TRUE;
};
EXITS Eof => NULL;
};
IOOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ Controls.PopUpRequest[["Input/Output"], LIST[
-- 1 -- ["Write Lights", "Save light descriptions"],
-- 2 -- ["Read Lights", "Replace lights with file"],
-- 3 -- ["Merge Lights", "Add file lights to current"],
-- 4 -- ["Write Material", "Save material descriptions"],
-- 5 -- ["Read Material", "Replace materials with file"],
-- 6 -- ["Merge Material", "Add file materials to current"],
-- 7 -- ["Write Lights and Materials", "Save lights and materials"],
-- 8 -- ["Read Lights and Materials", "Replace lights and materials"],
-- 9 -- ["Merge Lights and Materials", "Add lights and materials"],
-- 10 -- ["Read Image File", "Read a new image file"]
]];
SELECT choice FROM
1 => WriteLights[lt];
2 => ReadLights[lt, FALSE];
3 => ReadLights[lt, TRUE];
4 => WriteMaterials[lt];
5 => ReadMaterials[lt, FALSE];
6 => ReadMaterials[lt, TRUE];
7 => WriteLightsAndMaterials[lt];
8 => ReadLightsAndMaterials[lt, FALSE]; 
9 => ReadLightsAndMaterials[lt, TRUE]; 
10 => UsersFile[lt]; 
ENDCASE;
SELECT choice FROM
2 => LightChanged[lt];
3 => LightChanged[lt];
5 => LightChanged[lt];
6 => LightChanged[lt];
8 => LightChanged[lt];
9 => LightChanged[lt];
10 => LightChanged[lt];
ENDCASE;
};
SelectLightOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ ChooseLight[lt.lights, "Lights", FALSE];
IF choice > 0 THEN {
Buttons.ReLabel[parent, lt.lights[choice-1].name];
};
};
SelectMaterialOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ ChooseMaterial[lt.materials, "Materials"];
IF choice > 0 THEN {
Buttons.ReLabel[parent, lt.materials[choice-1].name];
};
};
DebugOps: ClickProc ~ {
lt: LightingTool ¬ NARROW[clientData];
choice: INT ¬ Controls.PopUpRequest[["Debug options"], LIST[
-- 1 -- ["Redraw sphere", "Redraw sphere"],
-- 2 -- ["Redraw sphere", "Redraw 24bit sphere"]
]];
SELECT choice FROM
1 =>  lt.state.redrawSphere ¬ NOT TRUE;
2 =>  lt.state.redrawSphere ¬ lt.state.use24bits ¬ TRUE;
ENDCASE;
SELECT choice FROM
1 =>  LightChanged[lt];
2 =>  { ViewerOps.PaintViewer[lt.outer, client]; CtMap.Mono[]; };
ENDCASE;
};
Function Click-Response Procedures
LightSpectrumFunction: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
};
MaterialSpectrumFunction: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
};
Control Click-Response Procedures
PsiControl: Controls.ControlProc ~ {
theta, psi: REAL;
lt: LightingTool ¬ NARROW[control.clientData];
IF GotAnActiveLight[lt] THEN {
[theta, psi] ¬ GetActiveAngles[lt];
psi ¬ NARROW[control.value];
SetActiveAngles[lt, theta, psi];
LightChanged[lt];
};
};
ThetaControl: Controls.ControlProc ~ {
theta, psi: REAL;
lt: LightingTool ¬ NARROW[control.clientData];
IF GotAnActiveLight[lt] THEN {
[theta, psi] ¬ GetActiveAngles[lt];
theta ¬ NARROW[control.value];
SetActiveAngles[lt, theta, psi];
LightChanged[lt];
};
};
SpreadControl: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
IF GotAnActiveLight[lt] THEN {
lt.lights[lt.activeLight].spread ¬ control.value;
LightChanged[lt];
};
};
SurfaceControls: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
};
LightColorSliders: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
lt.lights[lt.activeLight].color.rgb ¬ GetLightSlidersRGB[lt];
LightChanged[lt];
};
MaterialColorSliders: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
lt.materials[lt.activeMaterial].color.rgb ¬ GetMaterialSlidersRGB[lt];
MaterialChanged[lt];
};
MaterialControls: Controls.ControlProc ~ {
lt: LightingTool ¬ NARROW[control.clientData];
mp: MaterialProps ¬ lt.materials[lt.activeMaterial].materialProps;
IF Rope.Equal[control.name, lt.kdControl.name] THEN mp.kd ¬ control.value;
IF Rope.Equal[control.name, lt.ksControl.name] THEN mp.ks ¬ control.value;
IF Rope.Equal[control.name, lt.krControl.name] THEN mp.kr ¬ control.value;
IF Rope.Equal[control.name, lt.keControl.name] THEN mp.ke ¬ control.value;
IF Rope.Equal[control.name, lt.ktControl.name] THEN mp.kt ¬ control.value;
IF Rope.Equal[control.name, lt.kmControl.name] THEN mp.km ¬ control.value;
IF Rope.Equal[control.name, lt.kcControl.name] THEN mp.kc ← control.value;
lt.materials[lt.activeMaterial].materialProps ¬ mp;
MaterialChanged[lt];
};
Control Update Procedures
UpdateLightAngles: PROC [lt: LightingTool] ~ {
IF GotAnActiveLight[lt] THEN {
theta, psi: REAL;
[theta, psi] ¬ GetActiveAngles[lt];
Controls.SetSliderDialValue[lt.thetaControl, theta];
Controls.SetSliderDialValue[lt.psiControl, psi];
};
};
UpdateLightColorSliders: PROC [lt: LightingTool] ~ {
hsl: HSL ¬ ImagerColorFns.HSLFromRGB[lt.lights[lt.activeLight].color.rgb];
Controls.SetSliderDialValue[lt.lightColorSlider1, hsl.H];
Controls.SetSliderDialValue[lt.lightColorSlider2, hsl.S];
Controls.SetSliderDialValue[lt.lightColorSlider3, hsl.L];
CtMap.WriteEntry[1, RGBuTos[GetLightSlidersRGB[lt]]];
};
UpdateMaterialColorSliders: PROC [lt: LightingTool] ~ {
hsl: HSL ¬ ImagerColorFns.HSLFromRGB[lt.materials[lt.activeMaterial].color.rgb];
Controls.SetSliderDialValue[lt.materialColorSlider1, hsl.H];
Controls.SetSliderDialValue[lt.materialColorSlider2, hsl.S];
Controls.SetSliderDialValue[lt.materialColorSlider3, hsl.L];
CtMap.WriteEntry[1, RGBuTos[GetMaterialSlidersRGB[lt]]];
};
UpdateMaterialSliders: PROC [lt: LightingTool] ~ {
mp: MaterialProps ¬ lt.materials[lt.activeMaterial].materialProps;
Controls.SetSliderDialValue[lt.kdControl, mp.kd, TRUE];
Controls.SetSliderDialValue[lt.ksControl, mp.ks, TRUE];
Controls.SetSliderDialValue[lt.krControl, mp.kr, TRUE];
Controls.SetSliderDialValue[lt.keControl, mp.ke, TRUE];
Controls.SetSliderDialValue[lt.ktControl, mp.kt, TRUE];
Controls.SetSliderDialValue[lt.kmControl, mp.km, TRUE];
Controls.SetSliderDialValue[lt.kcControl, mp.kc, TRUE];
};
Drawing Procedures
LightingDraw: Draw2d.DrawProc ~ {
lt: LightingTool ~ NARROW[clientData];
DrawSphere: PROC ~ {
outer: Viewer ¬ lt.outer;
graphics: Viewer ¬ lt.graphics;
ditherMode: BOOLEAN ¬ TRUE;
SELECT whatChanged FROM
NIL, $Redraw => {
Inner: PROC [rgMap, bMap: SampleMap] ~ {
box: ImagerSample.Box ¬ ImagerSample.GetBox[rgMap];
size: ImagerSample.Vec ¬ ImagerSample.GetSize[rgMap];
rgScanLine: ImagerSample.SampleBuffer¬ImagerSample.ObtainScratchSamples[size.f];
bScanLine: ImagerSample.SampleBuffer¬ImagerSample.ObtainScratchSamples[size.f];
diameter: INTEGER ¬ MIN[box.max.s-box.min.s, box.max.f-box.min.f];
radius: INTEGER ¬ Real.Floor[diameter/2.0];
iradius: REAL ¬ 1.0/radius;
boxLeft: INTEGER ¬ box.min.f + 10;
boxTop: INTEGER ¬ box.min.s + Real.Floor[((box.max.s-box.min.s)-diameter)/2.0];
center: Pair ¬ [boxLeft+radius, boxTop+radius];
FOR y: INTEGER IN [boxTop..boxTop+diameter) DO
Process.CheckForAbort[];
ImagerSample.GetSamples[rgMap, [y, box.min.f],, rgScanLine, 0, size.f];
ImagerSample.GetSamples[bMap, [y, box.min.f],, bScanLine, 0, size.f];
FOR x: NAT IN [boxLeft..boxLeft+diameter) DO
dy: REAL ← (y-center.y) * iradius;
dx: REAL ← (x-center.x) * iradius;
pval: INT ← EncodeNormal[lt, dx, dy, ditherMode];
dx, dy: REAL;
pval: INT;
IF x=275 AND y=88 THEN {
dx ¬ 5.0;
};
dy ¬ (y-center.y) * iradius;
dx ¬ (x-center.x) * iradius;
pval ¬ G3dNormalCoding.EncodeNormal[dx, dy, ditherMode];
pval ¬ MAX[0, pval];
rgScanLine[x] ¬ pval+256*pval;
bScanLine[x] ¬ pval;
ENDLOOP;
ImagerSample.PutSamples[rgMap, [y, box.min.f],, rgScanLine, 0, size.f];
ImagerSample.PutSamples[bMap, [y, box.min.f],, bScanLine, 0, size.f];
ENDLOOP;
ImagerSample.ReleaseScratchSamples[rgScanLine];
ImagerSample.ReleaseScratchSamples[bScanLine];
};
maps: SampleMaps; -- ¬ CtBasic.GetColorDisplayMaps[];
x: NAT ¬ lt.outer.cx+lt.graphics.cx;
y: NAT ¬ (ViewerSpecs.colorScreenHeight-1)-(lt.outer.wy+lt.graphics.cy+lt.graphics.ch);
w: NAT ¬ lt.graphics.cw;
h: NAT ¬ lt.graphics.ch;
CtBasic.ReIndexMaps[maps: maps, box: [[x, y], [x+w, y+h]]];
Inner[maps[0].map, maps[1].map];
};
ENDCASE;
};
Draw24bitSphere: PROC ~ {
outer: Viewer ¬ lt.outer;
graphics: Viewer ¬ lt.graphics;
ditherMode: BOOLEAN ¬ TRUE;
SELECT whatChanged FROM
NIL, $Redraw => {
Inner: PROC [rgMap, bMap: SampleMap] ~ {
box: ImagerSample.Box ¬ ImagerSample.GetBox[rgMap];
size: ImagerSample.Vec ¬ ImagerSample.GetSize[rgMap];
rgScanLine: ImagerSample.SampleBuffer¬ImagerSample.ObtainScratchSamples[size.f];
bScanLine: ImagerSample.SampleBuffer¬ImagerSample.ObtainScratchSamples[size.f];
center: Pair ¬ [214.0, 276.0];
diameter: INTEGER ¬ 2 * lt.state.radius;
boxTop: INTEGER ¬ Real.Round[center.y - lt.state.radius];
boxLeft: INTEGER ¬ Real.Round[center.x - lt.state.radius];
iradius: REAL ¬ 1.0/lt.state.radius;
FOR y: INTEGER IN [boxTop..boxTop+diameter) DO
Process.CheckForAbort[];
ImagerSample.GetSamples[rgMap, [y, box.min.f],, rgScanLine, 0, size.f];
ImagerSample.GetSamples[bMap, [y, box.min.f],, bScanLine, 0, size.f];
FOR x: NAT IN [boxLeft..boxLeft+diameter) DO
dy: REAL ¬ (y-center.y) * iradius;
dx: REAL ¬ (x-center.x) * iradius;
dz: REAL ¬ 1.0 - ((dx*dx)+(dy*dy));
IF dz >= 0.0
THEN {
srgb: SRGB;
dz ¬ -RealFns.SqRt[dz];
srgb ¬ ShadeNormal[lt, [dx, dy, dz]];
rgScanLine[x] ¬ (srgb.r * 256) + srgb.g;
bScanLine[x] ¬ srgb.b;
}
ELSE {
rgScanLine[x] ¬ bScanLine[x] ¬ 0;
};
ENDLOOP;
ImagerSample.PutSamples[rgMap, [y, box.min.f],, rgScanLine, 0, size.f];
ImagerSample.PutSamples[bMap, [y, box.min.f],, bScanLine, 0, size.f];
ENDLOOP;
ImagerSample.ReleaseScratchSamples[rgScanLine];
ImagerSample.ReleaseScratchSamples[bScanLine];
};
maps: SampleMaps; -- ¬ CtBasic.GetColorDisplayMaps[];
x: NAT ¬ lt.outer.cx+lt.graphics.cx;
y: NAT ¬ (ViewerSpecs.colorScreenHeight-1)-(lt.outer.wy+lt.graphics.cy+lt.graphics.ch);
w: NAT ¬ lt.graphics.cw;
h: NAT ¬ lt.graphics.ch;
CtBasic.ReIndexMaps[maps: maps, box: [[x, y], [x+w, y+h]]];
Inner[maps[0].map, maps[1].map];
};
ENDCASE;
};
BuildColorPatch: PROC ~ {
Inner: PROC [b: Box, c: INT, srgb: SRGB, showFlag: BOOLEAN] ~ {
IF showFlag
THEN {
CtBasic.PutRGBBox[maps, b.min.f, b.min.s, b.max.f, b.max.s, [0, 0, 0]];
IF lt.state.use24bits
THEN CtBasic.PutRGBBox[maps, b.min.f+1, b.min.s+1, b.max.f-1, b.max.s-1, srgb]
ELSE CtBasic.PutRGBBox[maps, b.min.f+1,b.min.s+1,b.max.f-1,b.max.s-1, [c,c,c]];
}
ELSE
CtBasic.PutRGBBox[maps,b.min.f,b.min.s,b.max.f,b.max.s,[bgColor,bgColor,bgColor]];
};
maps: SampleMaps; -- ¬ CtBasic.GetColorDisplayMaps[];
Inner[
lt.lightColorPatch,
lightColor,
IF GotAnActiveLight[lt]
THEN RGBuTos[lt.lights[lt.activeLight].color.rgb] ELSE [255, 255, 255],
lt.lights # NIL AND lt.lights.length > 0];
Inner[
lt.materialColorPatch,
materialColor,
IF GotAnActiveMaterial[lt]
THEN RGBuTos[lt.materials[lt.activeMaterial].color.rgb] ELSE [255, 255, 255],
lt.materials # NIL AND lt.materials.length > 0];
};
DrawFile: PROC ~ {
IF lt.state.maps = NIL THEN ReadFile[lt, lt.state.imageFileName];
CtBasic.ShowMaps[lt.state.maps];
};
IF ColorDisplayManager.NextState[NIL].level = viewers THEN {
[] ¬ Terminal.ModifyColorFrame[Terminal.Current[], BuildColorPatch];
IF lt.state.use24bits
THEN [] ¬ Terminal.ModifyColorFrame[Terminal.Current[], Draw24bitSphere]
ELSE [] ¬ Terminal.ModifyColorFrame[Terminal.Current[], DrawFile];
[] ← Terminal.ModifyColorFrame[Terminal.Current[], DrawSphere];
lt.state.redrawSphere ¬ FALSE;
lt.state.use24bits ¬ FALSE;
lt.state.radius ¬ 203;
};
};
UsersFile: PROC [lt: LightingTool] ~ {
Inner: PROC ~ {
ReadFile[lt, fileName];
r: ROPE ← FileNames.ResolveRelativePath[fileName];
map: SampleMap ← CtFile.Read8BitFile[r, 0, 0, 405, 405][0].map;
lt.state.maps ← CtBasic.CreateMaps[24, 0, 0, 405, 405, FALSE];
lt.state.maps[1].map ← map;
lt.state.maps[0].map ← CtBasic.RGFromRedAndGrn[map, map];
CtBasic.ReIndexMaps[lt.state.maps, [73, 12]];
CtBasic.ShowMaps[lt.state.maps];
};
fileName: ROPE ¬ Controls.TypescriptReadFileName[lt.ts];
[] ¬ Terminal.ModifyColorFrame[Terminal.Current[], Inner];
};
Mouse Procedures
LightingMouse: Controls.MouseProc ~ {
lt: LightingTool ~ NARROW[clientData];
IF mouse.state # down AND mouse.state # held THEN RETURN;
Controls.TypescriptWrite[lt.outerData.typescript, IO.PutFR["mouse at (%g, %g)",
IO.int[graphicsData.mouse.pos.x], IO.int[graphicsData.mouse.pos.y]]];
SELECT TRUE FROM
graphicsData.mouse.state = none => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" state: none"]];
graphicsData.mouse.state = down => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" state: down"]];
graphicsData.mouse.state = held => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" state: held"]];
graphicsData.mouse.state = up => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" state: up"]];
ENDCASE;
SELECT TRUE FROM
graphicsData.mouse.button = none => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" button: none\n"]];
graphicsData.mouse.button = left => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" button: left\n"]];
graphicsData.mouse.button = middle => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" button: middle\n"]];
graphicsData.mouse.button = right => Controls.TypescriptWrite[lt.outerData.typescript,IO.PutFR[" button: right\n"]];
ENDCASE;
IF lt.state.nextPressSpecial THEN {
IF lt.state.pointDirection THEN NewLightDirection[lt, mouse];
IF lt.state.pointHighlight THEN NewLightHighlight[lt, mouse];
UpdateLightAngles[lt];
};
IF lt.state.nextPressSpecial THEN
[] ¬ Terminal.SetColorCursorPresentation[Terminal.Current[], $onesAreBlack];
};
Utilities
RGBuTos: PROC [urgb: URGB] RETURNS [srgb: SRGB] ~ {
srgb.r ¬ Real.Floor[MIN[255.0, MAX[0.0, 255.0 * urgb.R]]];
srgb.g ¬ Real.Floor[MIN[255.0, MAX[0.0, 255.0 * urgb.G]]];
srgb.b ¬ Real.Floor[MIN[255.0, MAX[0.0, 255.0 * urgb.B]]];
};
RGBsTou: PROC [srgb: SRGB] RETURNS [u: URGB] ~ {u¬[srgb.r/255.0,srgb.g/255.0,srgb.b/255.0]};
Miscellaneous Procedures
LightingDestroy: Controls.DestroyProc ~ {CtMap.Mono[]};
Light Placement Commands
DirectionPoint: PROC [lt: LightingTool] ~ {
lt.state.nextPressSpecial ¬ TRUE;
lt.state.pointDirection ¬ TRUE;
lt.state.pointHighlight ¬ FALSE;
[] ¬ Terminal.SetColorCursorPresentation[Terminal.Current[], $onesAreWhite];
};
HighlightPoint: PROC [lt: LightingTool] ~ {
lt.state.nextPressSpecial ¬ TRUE;
lt.state.pointHighlight ¬ TRUE;
lt.state.pointDirection ¬ FALSE;
[] ¬ Terminal.SetColorCursorPresentation[Terminal.Current[], $onesAreWhite];
};
Mouse Light Placement Procedures
NewLightDirection: PROC [lt: LightingTool, mouse: Mouse] ~ {
norm: Triple ¬ GetNormalFromPixel[lt, mouse ! NotANormal => GOTO NoNormal];
we assume that all three colors contain the same value
IF lt.lights # NIL AND lt.lights.length > 0 THEN {
norm ¬ G3dVector.Negate[norm];
IF lt.state.placeInvertZ THEN norm.z ¬ -norm.z;
lt.lights[lt.activeLight].direction ¬ norm;
LightChanged[lt];
};
EXITS
NoNormal => NULL;
};
NewLightHighlight: PROC [lt: LightingTool, mouse: Mouse] ~ {
norm: Triple ¬ GetNormalFromPixel[lt, mouse ! NotANormal => GOTO NoNormal];
dir: Triple;
-- we assume that all three colors contain the same value
IF lt.lights # NIL AND lt.lights.length > 0 THEN {
norm ¬ G3dVector.Negate[norm];
IF lt.state.placeInvertZ THEN norm.z ¬ -norm.z;
dir ¬ G3dVector.Sub[[0, 0, 1.0], G3dVector.Mul[norm, 2.0]];
lt.lights[lt.activeLight].direction ¬ G3dVector.Mul[G3dVector.Unit[dir], -1.0];
LightChanged[lt];
};
EXITS
NoNormal => NULL;
};
Shading Procedures
LightChanged: PROC [lt: LightingTool] ~ {
lt.lights[lt.activeLight] ← RebuildLight[lt.lights[lt.activeLight], lt.materials[lt.activeMaterial]];
BuildColormap[lt];
};
MaterialChanged: PROC [lt: LightingTool] ~ {BuildColormap[lt]};
BuildColormap: PROC [lt: LightingTool] ~ {
cm: Cmap ¬ CtMap.NewCmap[];
IF lt.state.redrawSphere THEN ViewerOps.PaintViewer[lt.outer, client];
IF lt.lights # NIL AND lt.materials # NIL AND lt.lights.length > 0 AND lt.materials.length > 0
THEN {
FOR entry: INT IN [0 .. 255] DO
IF G3dNormalCoding.IsValidNormalIndex[entry]
THEN CtMap.SetEntry[cm, entry, ShadeNormal[lt, G3dNormalCoding.DecodeNormal[entry]]]
ELSE CtMap.SetEntry[cm, entry, [0, 0, 0]];
ENDLOOP;
}
ELSE {
FOR entry: INT IN [0 .. 255] DO
CtMap.SetEntry[cm, entry, [128, 128, 128]];
ENDLOOP;
};
CtMap.SetEntry[cm, fgColor, [0, 0, 0]];
CtMap.SetEntry[cm, lightColor, RGBuTos[GetLightSlidersRGB[lt]]];
CtMap.SetEntry[cm, materialColor, RGBuTos[GetMaterialSlidersRGB[lt]]];
CtMap.SetEntry[cm, bgColor, [255, 255, 255]];
CtMap.WriteAndRelease[cm];
};
NewBuildColormap: PROC [lt: LightingTool] ~ {
cm: Cmap ¬ CtMap.NewCmap[];
IF lt.state.redrawSphere THEN ViewerOps.PaintViewer[lt.outer, client];
IF lt.lights # NIL AND lt.materials # NIL AND lt.lights.length > 0 AND lt.materials.length > 0
THEN {
FOR entry: INT IN [0 .. 255] DO
CtMap.SetEntry[cm, entry, ShadeIndex[lt, entry]];
ENDLOOP;
}
ELSE {
FOR entry: INT IN [0 .. 255] DO
CtMap.SetEntry[cm, entry, [128, 128, 128]];
ENDLOOP;
};
CtMap.SetEntry[cm, fgColor, [0, 0, 0]];
CtMap.SetEntry[cm, lightColor, RGBuTos[GetLightSlidersRGB[lt]]];
CtMap.SetEntry[cm, materialColor, RGBuTos[GetMaterialSlidersRGB[lt]]];
CtMap.SetEntry[cm, bgColor, [255, 255, 255]];
CtMap.WriteAndRelease[cm];
};
GetLightSlidersRGB: PROC [lt: LightingTool] RETURNS [rgb: URGB] ~ {
rgb.R ← NARROW[lt.lightColorSlider1.value];
rgb.G ← NARROW[lt.lightColorSlider2.value];
rgb.B ← NARROW[lt.lightColorSlider3.value];
rgb ¬ ImagerColor.RGBFromColor[ImagerColor.ColorFromHSV[lt.lightColorSlider1.value, lt.lightColorSlider2.value, lt.lightColorSlider3.value]];
};
GetMaterialSlidersRGB: PROC [lt: LightingTool] RETURNS [rgb: URGB] ~ {
rgb.R ← NARROW[lt.materialColorSlider1.value];
rgb.G ← NARROW[lt.materialColorSlider2.value];
rgb.B ← NARROW[lt.materialColorSlider3.value];
rgb ¬ ImagerColor.RGBFromColor[ImagerColor.ColorFromHSV[lt.materialColorSlider1.value, lt.materialColorSlider2.value, lt.materialColorSlider3.value]];
};
Shade with a model described by the following equations:
(1) E = kd Mc (La + S [Difi]) + ks S [Speci]
(2a) di = N . Si ; qi = Acos(di)
(2b) did = cos(qd) ; qid = q/spreadi
(2c) dis = cos(qs) ; qis = q/kc
(3) Difi = Li did
(4) Speci = (E . Ri)ke Hi
(4a) Ri = 2N - Si
(5a) Hi = km Fi + (1-km) Li
(5b) Fi = dis Mc Li + (1-dis) Li
Equations (5) may be rewritten as
(5') Hi = Li (1 - km dis) + (km dis) (Mc Li)
where 
 1 E  is the emitted light
  kd  is the material diffuse reflection coefficient
  Mc is the material color
  La  is the ambient light color
  Difi is the diffuse contribution of light source i
  ks  is the material specular reflection coefficient
  Speci is the specular contribution of light source i
  
 2 di  is the cosine of the incident angle with light source i
  N  is the surface normal at the shading point
  Si  is the vector from the shading point to light source i
  qi  is the angle between the normal and light source i
  qid is the spread diffuse angle between the normal and light source i
 spreadi is the material diffuse spread factor
  did is the cosine of qid
  qis is the spread specular angle between the normal and light source i
  kc  is the material specular spread factor
  dis is the cosine of qis
 3 Li  is the color of light source i
 4 Ri  is the reflected direction of the light
 5 Hi  is the color of the highlight of light source i
  km is the material metallic coefficient
  Fi  is the "faked" Fresnel color-shifted highlight
  
Note that the color shifting computed in Speci is within the summation, since it is dependent on the color and angle of incidence of each light source  
ShadeNormal: PROC [lt: LightingTool, norm: Triple] RETURNS [srgb: SRGB] ~ {
diffuse, specular, total: URGB ¬ [0.0, 0.0, 0.0];
kd: REAL ¬ lt.materials[lt.activeMaterial].materialProps.kd;
ks: REAL ¬ lt.materials[lt.activeMaterial].materialProps.ks;
ke: REAL ¬ lt.materials[lt.activeMaterial].materialProps.ke;
km: REAL ¬ lt.materials[lt.activeMaterial].materialProps.km;
kc: REAL ¬ lt.materials[lt.activeMaterial].materialProps.kc;
ref: Triple;
white: URGB ¬ [1.0, 1.0, 1.0];
matclr: URGB ¬ lt.materials[lt.activeMaterial].color.rgb;
refdot: REAL;
IF lt.lights # NIL THEN {
FOR n: NAT IN [0..lt.lights.length) DO
l: Light ¬ lt.lights[n];
lightclr: URGB ¬ l.color.rgb;
IF Rope.Equal[s1: l.name, s2: "Ambient", case: FALSE]
THEN {
diffuse.R ¬ diffuse.R + lightclr.R;
diffuse.G ¬ diffuse.G + lightclr.G;
diffuse.B ¬ diffuse.B + lightclr.B;
}
ELSE {
direction: Triple ¬ [l.direction.x, l.direction.y, l.direction.z];
di: REAL ¬ G3dVector.Dot[norm, G3dVector.Negate[direction]];
IF di > 0.0 THEN {
diffuse ¬ AddColors[diffuse, MulColor[lightclr, MyPower[di, l.spread]]];
ref ¬ G3dVector.Add[direction, G3dVector.Mul[norm, 2.0*di]];
-- since E = (0, 0, -1), the result of (E . Ri) is simply -(Ri)z
refdot ¬ -ref.z;
IF refdot > 0.0 THEN {
specscl: REAL ¬ MyPower[refdot, l.spread * ke];
difclr: URGB ¬ SubColors[matclr, white];
modclr: URGB ¬ MulColor[difclr, km*MyPower[di, kc]];
comclr: URGB ¬ AddColors[modclr, white];
hiclr: URGB ¬ ScaleColor[comclr, lightclr];
g: REAL ← km * dis;
hiclr ← AddColors[
MulColor[lightclr, 1.0-g], MulColor[ScaleColor[lightclr, matclr], g]];
specular ¬ AddColors[specular, MulColor[hiclr, specscl]];
};
};
};
ENDLOOP;
};
-- compute (kd Mc diffuse)
diffuse ¬ ScaleColor[diffuse, matclr];
diffuse ¬ MulColor[diffuse, kd];
-- compute (ks specular)
specular ¬ MulColor[specular, ks];
-- add together the two components
total ¬ AddColors[diffuse, specular];
srgb ¬ RGBuTos[total];
};
ScaleColor: PROC [clr, scl: URGB] RETURNS [new: URGB] ~ {
new.R ¬ clr.R * scl.R; new.G ¬ clr.G * scl.G; new.B ¬ clr.B * scl.B;
};
MulColor: PROC [clr: URGB, mul: REAL] RETURNS [new: URGB] ~ {
new.R ¬ clr.R * mul; new.G ¬ clr.G * mul; new.B ¬ clr.B * mul;
};
AddColors: PROC [a, b: URGB] RETURNS [new: URGB] ~ {
new.R ¬ a.R + b.R; new.G ¬ a.G + b.G; new.B ¬ a.B + b.B;
};
SubColors: PROC [a, b: URGB] RETURNS [new: URGB] ~ {
new.R ¬ a.R - b.R; new.G ¬ a.G - b.G; new.B ¬ a.B - b.B;
};
ShadeIndex: PROC [lt: LightingTool, index: INT] RETURNS [SRGB] ~ {
kd: REAL ¬ lt.materials[lt.activeMaterial].materialProps.kd;
ks: REAL ¬ lt.materials[lt.activeMaterial].materialProps.ks;
matclr: URGB ¬ lt.materials[lt.activeMaterial].color.rgb;
total, diffuse, specular: URGB ¬ [0, 0, 0];
IF NOT G3dNormalCoding.IsValidNormalIndex[index] THEN RETURN [[0,0,0]];
IF lt.lights = NIL THEN RETURN [[0,0,0]];
FOR n: NAT IN [0..lt.lights.length) DO
l: Light ¬ lt.lights[n];
IF Rope.Equal[s1: l.name, s2: "Ambient", case: FALSE]
THEN diffuse ¬ AddColors[diffuse, l.color.rgb]
ELSE {
diffuse ¬ AddColors[diffuse, l.accelerators.cacheD[index].rgb];
specular ¬ AddColors[specular, l.accelerators.cacheS[index].rgb];
};
ENDLOOP;
diffuse ¬ ScaleColor[MulColor[diffuse, kd], matclr];
specular ¬ MulColor[specular, ks];
total ¬ AddColors[diffuse, specular];
RETURN [RGBuTos[total]];
};
Normal Extraction
GetPixelRGB: PROC [lt: LightingTool, mouse: Mouse] RETURNS [srgb: SRGB] ~ {
maps: SampleMaps ¬ CtBasic.GetColorDisplayMaps[];
srgb ¬ CtBasic.GetRGBPixel[maps, mouse.pos.x, 479-mouse.pos.y];
};
GetNormalFromPixel: PROC [lt: LightingTool, mouse: Mouse] RETURNS [norm: Triple] ~ {
Inner: PROC ~ {
pval: ARRAY [0..25) OF INT;
maps: SampleMaps -- ¬ CtBasic.GetColorDisplayMaps[] -- ;
index: INT ¬ 0;
badnorm: BOOLEAN ¬ FALSE;
FOR py: INT IN [mouse.pos.y-2 .. mouse.pos.y+2] DO
FOR px: INT IN [mouse.pos.x-2 .. mouse.pos.x+2] DO
IF px>=0 AND py>=0 AND px<640 AND py<480
THEN {
srgb: SRGB ¬ CtBasic.GetRGBPixel[maps, px, 479-py];
IF NOT IsValidNormalPixel[srgb]
THEN badnorm ¬ TRUE;
pval[index] ¬ srgb.r;
}
ELSE pval[index] ¬ 0;
index ¬ index+1;
ENDLOOP;
ENDLOOP;
IF badnorm THEN GOTO RaiseError;
norm ¬ [0.0, 0.0, 0.0];
FOR index: INT IN [0..25) DO
tnorm: Triple ¬ PvalToNorm[pval[index]];
norm.x ¬ norm.x + tnorm.x;
norm.y ¬ norm.y + tnorm.y;
norm.z ¬ norm.z + tnorm.z;
ENDLOOP;
norm ¬ G3dVector.Unit[norm];
EXITS RaiseError => ERROR NotANormal;
};
[] ¬ Terminal.ModifyColorFrame[Terminal.Current[], Inner];
};
PvalToNorm: PROC [pval: INT] RETURNS [t: Triple] ~{t¬G3dNormalCoding.DecodeNormal[pval]};
Accelerators
RebuildLight: PROC [l: Light, m: Material] RETURNS [Light] ~ {
di, pi: REAL;
lDi, lSi: URGB;
IF light.accelerators.cached = NIL THEN light.accelerators.cached ← NEW[RealArray];
IF light.accelerators.cacheD = NIL THEN light.accelerators.cacheD ← NEW[ColorArray];
IF light.accelerators.cachep = NIL THEN light.accelerators.cachep ← NEW[RealArray];
IF light.accelerators.cacheS = NIL THEN light.accelerators.cacheS ← NEW[ColorArray];
FOR i: INT IN [0 .. 255] DO
IF G3dNormalCoding.IsValidNormalIndex[i] THEN {
norm: Triple ¬ G3dNormalCoding.DecodeNormal[i];
di ¬ G3dVector.Dot[l.direction, norm];
IF di > 0.0
THEN {
lSi2, lSi3: URGB;
lDi ¬ MulColor[l.color.rgb, MyPower[di, l.spread]];
pi ¬ l.direction.z + (2.0 * norm.z * di); -- assume eye = [0, 0, 1]
lSi2 ¬ MulColor[m.color.rgb, di * m.materialProps.km];
lSi3 ¬ ScaleColor[AddColors[[1.0, 1.0, 1.0], lSi2], l.color.rgb];
lSi ¬ MulColor[lSi3, MyPower[pi, m.materialProps.ke * l.spread]];
}
ELSE {
di ¬ pi ¬ 0.0;
lDi ¬ lSi ¬ [0.0, 0.0, 0.0];
};
l.accelerators.cached[i] ¬ di;
l.accelerators.cacheD[i] ¬ [rgb: lDi];
l.accelerators.cachep[i] ¬ pi;
l.accelerators.cacheS[i] ¬ [rgb: lSi];
};
ENDLOOP;
RETURN[l];
};
Utilties
IsValidNormalPixel: PROC [srgb: SRGB] RETURNS [BOOL] ~ {
RETURN[G3dNormalCoding.IsValidNormalIndex[srgb.r]];
};
SuperEllipse: PROC [x, e: REAL] RETURNS [REAL] ~ {
IF e <= 0.0 THEN RETURN[1.0];
RETURN[RealFns.Exp[0.5*e*RealFns.Ln[1.0-RealFns.Power[x, 2.0/e]]]];
};
Perlin: PROC [u, v: REAL] RETURNS [REAL] ~ {
IF u <= 0.0 OR v <= 0.0 THEN RETURN[0.0];
RETURN[RealFns.Power[0.5, RealFns.Log[0.5, u]*RealFns.Log[0.5, v]]];
};
NuclearDecay: PROC [x0, k, t: REAL] RETURNS [r: REAL] ~ { -- xt = x0e-kt
RETURN[x0*RealFns.Exp[-k*t]];
};
MyPower: PROC [a, b: REAL] RETURNS [c: REAL] ~ {
d: REAL;
IF a < 1e-11 THEN RETURN [0.0];
IF b = 1.0 THEN RETURN [a];
d ¬ b * RealFns.Ln[a];
c ¬ IF d < -20.0 THEN 0.0 ELSE RealFns.Exp[d]; -- less than about .000000002 = 2.0e-9
};
Lighting Commands
AddLight: PROC [lt: LightingTool] ~ {
s: IO.STREAM ¬ IO.RIS[Controls.TypescriptRead[lt.ts, "new light name: "]];
name: ROPE ¬ IO.GetTokenRope[s ! IO.EndOfStream => GOTO Eof].token;
lt.lights ¬ AddToLightSequence[lt.lights,
[name, lt.lights[lt.activeLight].direction,
lt.lights[lt.activeLight].spread, lt.lights[lt.activeLight].color]];
lt.activeLight ¬ lt.lights.length - 1;
IF lt.lights.length = 1 THEN DrawLightingControls[lt];
EXITS Eof => NULL;
};
DeleteLight: PROC [lt: LightingTool] ~ {
lnum: INT;
IF lt.lights = NIL OR lt.lights.length = 0
THEN Blink["No lights."]
ELSE {
lnum ¬ ChooseLight[lt.lights, "Delete Light", TRUE]-1;
IF lnum = 0
THEN {
IF Confirm["delete all lights"] THEN {
lt.lights ¬ NIL;
EraseLightingControls[lt];
};
}
ELSE {
lnum ¬ lnum-1;
Controls.TypescriptWrite[lt.ts,
IO.PutFR1["Deleting light %g\n", IO.rope[lt.lights[lnum].name]]];
RemoveFromLightSequence[lnum, lt.lights];
IF lt.activeLight >= lnum THEN lt.activeLight ¬ MAX[0, lt.activeLight-1];
Controls.TypescriptWrite[lt.ts,
IO.PutFR1["New active light %g\n", IO.rope[lt.lights[lt.activeLight].name]]];
IF lt.lights.length = 0 THEN EraseLightingControls[lt];
LightChanged[lt];
};
};
};
ActivateLight: PROC [lt: LightingTool] ~ {
lnum: INT;
IF lt.lights = NIL OR lt.lights.length = 0
THEN Blink["No lights."]
ELSE {
lnum ¬ ChooseLight[lt.lights, "Activate Light", FALSE]-1;
IF lnum >= 0 THEN {
lt.activeLight ¬ lnum;
Controls.TypescriptWrite[lt.ts,
IO.PutFR1["Light %g active\n", IO.rope[lt.lights[lnum].name]]];
};
};
UpdateActiveLight[lt];
};
NormalizeLights: PROC [lt: LightingTool] ~ {
lights: LightSequence ¬ lt.lights;
clr: URGB ¬ [0, 0, 0];
IF lights # NIL THEN
FOR n: NAT IN [0..lights.length) DO
clr.R ¬ clr.R + lights[n].color.rgb.R;
clr.G ¬ clr.G + lights[n].color.rgb.G;
clr.B ¬ clr.B + lights[n].color.rgb.B;
ENDLOOP;
clr.R ¬ IF clr.R # 0.0 THEN 1.0/clr.R ELSE 1.0;
clr.G ¬ IF clr.G # 0.0 THEN 1.0/clr.G ELSE 1.0;
clr.B ¬ IF clr.B # 0.0 THEN 1.0/clr.B ELSE 1.0;
FOR n: NAT IN [0..lights.length) DO
lights[n].color.rgb.R ¬ lights[n].color.rgb.R * clr.R;
lights[n].color.rgb.G ¬ lights[n].color.rgb.G * clr.G;
lights[n].color.rgb.B ¬ lights[n].color.rgb.B * clr.B;
ENDLOOP;
UpdateActiveLight[lt];
};
Material Commands
AddMaterial: PROC [lt: LightingTool] ~ {
s: IO.STREAM ¬ IO.RIS[Controls.TypescriptRead[lt.ts, "new Material name: "]];
name: ROPE ¬ IO.GetTokenRope[s ! IO.EndOfStream => GOTO Eof].token;
lt.materials ¬ AddToMaterialSequence[lt.materials, [
name: name,
materialProps: lt.materials[lt.activeMaterial].materialProps,
color: lt.materials[lt.activeMaterial].color]];
lt.activeMaterial ¬ lt.materials.length - 1;
IF lt.materials.length = 1 THEN DrawMaterialControls[lt];
EXITS Eof => NULL;
};
DeleteMaterial: PROC [lt: LightingTool] ~ {
lnum: INT;
IF lt.materials = NIL OR lt.materials.length = 0
THEN Blink["No materials."]
ELSE {
lnum ¬ ChooseMaterial[lt.materials, "Delete Material"]-1;
IF lnum = 0
THEN {
IF Confirm["delete all materials"] THEN {
lt.materials ¬ NIL;
EraseMaterialControls[lt];
};
}
ELSE {
lnum ¬ lnum-1;
Controls.TypescriptWrite[lt.ts,
IO.PutFR1["Deleting material %g\n", IO.rope[lt.materials[lnum].name]]];
RemoveFromMaterialSequence[lnum, lt.materials];
IF lt.activeMaterial >= lnum THEN lt.activeMaterial ¬ MAX[0, lt.activeMaterial-1];
Controls.TypescriptWrite[lt.ts,
IO.PutFR1["New active material %g\n", IO.rope[lt.materials[lt.activeMaterial].name]]];
IF lt.materials.length = 0 THEN EraseMaterialControls[lt];
MaterialChanged[lt];
};
};
};
ActivateMaterial: PROC [lt: LightingTool] ~ {
lnum: INT;
IF lt.materials = NIL OR lt.materials.length = 0
THEN Blink["No materials."]
ELSE {
lnum ¬ ChooseMaterial[lt.materials, "Activate Material"]-1;
IF lnum >= 0 THEN {
lt.activeMaterial ¬ lnum;
Controls.TypescriptWrite[lt.ts,
IO.PutFR1["Material %g active\n", IO.rope[lt.materials[lnum].name]]];
};
};
UpdateActiveMaterial[lt];
};
Light Procs
ChooseLight: PROC [lights: LightSequence, header: ROPE, all: BOOL] RETURNS [choice: INT] ~ {
IF lights # NIL AND lights.length > 0 THEN {
popUps: LIST OF Controls.Request ¬ NIL;
FOR n: NAT IN [1..lights.length] DO
popUps ¬ CONS[[lights[lights.length-n].name], popUps];
ENDLOOP;
IF all THEN popUps ¬ CONS[["All Lights"], popUps];
choice ¬ Controls.PopUpRequest[[header], popUps];
};
};
GetActiveAngles: PROC [lt: LightingTool] RETURNS [theta, psi: REAL] ~ {
l: Light ¬ lt.lights[lt.activeLight];
d: REAL ¬ RealFns.SqRt[(l.direction.x * l.direction.x) + (l.direction.z * l.direction.z)];
theta ¬ RealFns.ArcTan[l.direction.x, l.direction.z];
psi ¬ RealFns.ArcTan[l.direction.y, d];
};
SetActiveAngles: PROC [lt: LightingTool, theta, psi: REAL] ~ {
d: REAL ¬ RealFns.Cos[psi];
lt.lights[lt.activeLight].direction.y ¬ RealFns.Sin[psi];
lt.lights[lt.activeLight].direction.x ¬ d * RealFns.Sin[theta];
lt.lights[lt.activeLight].direction.z ¬ d * RealFns.Cos[theta];
};
UpdateActiveLight: PROC [lt: LightingTool] ~ {
l: Light ¬ lt.lights[lt.activeLight];
UpdateLightAngles[lt];
UpdateLightColorSliders[lt];
};
GotAnActiveLight: PROC [lt: LightingTool] RETURNS [BOOLEAN] ~ {
RETURN[lt.lights # NIL AND lt.activeLight >= 0 AND lt.activeLight <= INTEGER[lt.lights.length]];
};
Material Procs
ChooseMaterial: PROC [materials: MaterialSequence, header: ROPE] RETURNS [choice: INT] ~ {
IF materials # NIL AND materials.length > 0 THEN {
popUps: LIST OF Controls.Request ¬ NIL;
FOR n: NAT IN [1..materials.length] DO
popUps ¬ CONS[[materials[materials.length-n].name], popUps];
ENDLOOP;
choice ¬ Controls.PopUpRequest[[header], popUps];
};
};
UpdateActiveMaterial: PROC [lt: LightingTool] ~ {
m: Material ¬ lt.materials[lt.activeMaterial];
UpdateMaterialSliders[lt];
UpdateMaterialColorSliders[lt];
};
GotAnActiveMaterial: PROC [lt: LightingTool] RETURNS [BOOLEAN] ~ {
RETURN[lt.materials # NIL AND lt.activeMaterial >= 0 AND lt.activeMaterial <= INTEGER[lt.materials.length]];
};
File I/O
Read24File: PROC [lt: LightingTool, name: ROPENIL] ~ {
IF name = NIL THEN name ← defaultSphereFile;
name ← FileNames.ResolveRelativePath[name];
lt.state.maps ← CtFile.Read24BitFile[name, name, name];
CtBasic.ReIndexMaps[lt.state.maps, [73, 12]];
};
ReadFile: PROC [lt: LightingTool, name: ROPE ¬ NIL] ~ {
r: ROPE ¬ FileNames.ResolveRelativePath[IF name = NIL THEN defaultSphereFile ELSE name];
map: SampleMap ← CtFile.Read8BitFile[r][0].map;
map: SampleMap ¬ CtFile.Read8BitFile[r, 0, 0, 405, 405][0].map;
lt.state.maps ¬ CtBasic.CreateMaps[24, 0, 0, 405, 405, FALSE];
lt.state.maps[1].map ¬ map;
lt.state.maps[0].map ¬ CtBasic.RGFromRedAndGrn[map, map];
CtBasic.ReIndexMaps[lt.state.maps, [73, 12]];
};
GetFileName: PROC [lt: LightingTool, prompt: ROPE] RETURNS [ROPE] ~ {
msg: ROPE ¬ IO.PutFR["%g: (dir = %g) ", IO.rope[prompt], IO.rope[lt.outerData.directory]];
r: ROPE ¬ Controls.TypescriptRead[lt.ts, msg];
IF r # NIL THEN {
c: CHAR ¬ Rope.Fetch[r, 0];
name: ROPE;
IF c # '[ AND c # '/ THEN r ¬ Rope.Concat[lt.outerData.directory, r];
name ¬ FileNames.ResolveRelativePath[r];
RETURN[name];
};
RETURN[NIL];
};
WriteLightsAndMaterials: PROC [lt: LightingTool] ~ {
fileName: ROPE ¬ GetFileName[lt, "Output file"];
w: Writer ¬
G3dIO.WriteCreate[Rope.Cat["Lights and materials file from ", program, " ", version]];
LightsToWriter[lt.lights, w];
G3dIO.WriteNode[w, "", "", TRUE];
MaterialsToWriter[lt.materials, w];
TiogaAccess.WriteFile[w, fileName! FS.Error => {Blink[error.explanation]; CONTINUE}]
};
ReadLightsAndMaterials: PROC [lt: LightingTool, merge: BOOLEAN] ~ {
name: ROPE ¬ GetFileName[lt, "Input file"];
in: STREAM ¬ FS.StreamOpen[name];
IF in = NIL THEN RETURN;
LightsFromStream[lt, in, merge];
MaterialsFromStream[lt, in, merge];
IO.Close[in];
};
WriteLights: PROC [lt: LightingTool] ~ {
fileName: ROPE ¬ GetFileName[lt, "Output light file"];
w: Writer ¬ G3dIO.WriteCreate[Rope.Cat["Lights file from ", program, " ", version]];
LightsToWriter[lt.lights, w];
TiogaAccess.WriteFile[w, fileName! FS.Error => {Blink[error.explanation]; CONTINUE}]
};
LightsToWriter: PROC [lights: LightSequence, w: Writer] ~ {
IF lights = NIL THEN RETURN;
G3dIO.WriteNode[w, "Lights", "i", TRUE];
TiogaAccess.Nest[w, 1];
FOR n: NAT IN [0..lights.length) DO
l: Light ¬ lights[n];
d: Triple ¬ l.direction;
c: URGB ¬ l.color.rgb;
IF NOT Rope.Equal[l.name, "Ambient"] THEN {
msg1: ROPE ¬ IO.PutFLR[" -name %g -position %g %g %g",
LIST[IO.rope[l.name], IO.real[d.x], IO.real[d.y], IO.real[d.z]]];
msg2: ROPE ¬ IO.PutFR[" -color %g %g %g", IO.real[c.R], IO.real[c.G], IO.real[c.B]];
msg3: ROPE ¬ Rope.Concat[msg1, msg2];
G3dIO.WritePartialNode[w, "AddLight", "b"];
G3dIO.WriteNode[w, msg3];
};
ENDLOOP;
FOR n: NAT IN [0..lights.length) DO
l: Light ¬ lights[n];
d: Triple ¬ l.direction;
c: URGB ¬ l.color.rgb;
IF Rope.Equal[l.name, "Ambient"] THEN {
msg: ROPE ¬ IO.PutFR[" %g %g %g", IO.real[c.R], IO.real[c.G], IO.real[c.B]];
G3dIO.WriteNode[w, "", "", TRUE];
G3dIO.WritePartialNode[w, "AmbientLight", "b"];
G3dIO.WriteNode[w, msg];
};
ENDLOOP;
TiogaAccess.Nest[w, -1];
};
ReadLights: PROC [lt: LightingTool, merge: BOOLEAN] ~ {
newone: BOOL ¬ FALSE;
origNum: INT ¬ IF lt.lights # NIL THEN lt.lights.length ELSE 0;
name: ROPE ¬ GetFileName[lt, "Input light file"];
in: STREAM ¬ FS.StreamOpen[name];
IF in = NIL THEN {
Blink[IO.PutFR1["Can't open file %g", IO.rope[name]]];
RETURN;
};
LightsFromStream[lt, in, merge];
IF origNum=0 AND lt.lights#NIL AND lt.lights.length>0 THEN DrawLightingControls[lt];
UpdateLightAngles[lt];
UpdateLightColorSliders[lt];
};
LightsFromStream: PROC [lt: LightingTool, stream: STREAM, merge: BOOLEAN] ~ {
IF NOT merge THEN lt.lights ¬ NIL;
{
DO
ENABLE
IO.EndOfStream => EXIT;
line: ROPE ¬ IO.GetLineRope[stream];
firstLetter: INT ¬ Rope.SkipOver[line, 0, " \t"];
firstWord: ROPE ¬ Rope.Substr[line, firstLetter, Rope.SkipTo[line, firstLetter, " \t"]-firstLetter];
IF Rope.Equal[firstWord, "AmbientLight", FALSE] THEN {
light: Light ¬ [];
cmdA, rA, gA, bA: Args.Arg;
[cmdA, rA, gA, bA] ¬ Args.ArgsGetFromRope[line, "%srrr"];
light.name ¬ "Ambient";
light.color.rgb ¬ [rA.real, gA.real, bA.real];
lt.lights ¬ AddToLightSequence[lt.lights, light];
};
IF Rope.Equal[firstWord, "AddLight", FALSE] THEN {
light: Light ¬ [];
cmdA, xA, yA, zA, nameA, rA, gA, bA: Args.Arg;
[cmdA, nameA, xA, yA, zA, rA, gA, bA] ¬ Args.ArgsGetFromRope[line, "%s-name%s-position%rrr-color%rrr"];
IF nameA.ok
THEN light.name ¬ nameA.rope
ELSE {
index: INT ¬ IF lt.lights # NIL THEN lt.lights.length ELSE 0;
light.name ¬ IO.PutFR1["Light%g", IO.int[index]];
};
IF xA.ok THEN light.direction ¬ [xA.real, yA.real, zA.real];
IF rA.ok THEN light.color.rgb ¬ [rA.real, gA.real, bA.real];
lt.lights ¬ AddToLightSequence[lt.lights, light];
};
ENDLOOP;
};
};
WriteMaterials: PROC [lt: LightingTool] ~ {
fileName: ROPE ¬ GetFileName[lt, "Output light file"];
w: Writer ¬ G3dIO.WriteCreate[Rope.Cat["Materials file from ", program, " ", version]];
MaterialsToWriter[lt.materials, w];
TiogaAccess.WriteFile[w, fileName! FS.Error => {Blink[error.explanation]; CONTINUE}]
};
MaterialsToWriter: PROC [materials: MaterialSequence, w: Writer] ~ {
IF materials = NIL THEN RETURN;
G3dIO.WriteNode[w, "Materials", "i", TRUE];
TiogaAccess.Nest[w, 1];
FOR n: NAT IN [0..materials.length) DO
m: Material ¬ materials[n];
mp: MaterialProps ¬ m.materialProps;
G3dIO.WriteNode[w, IO.PutFR1["%g Material", IO.rope[m.name]], "i", TRUE];
TiogaAccess.Nest[w, 1];
G3dIO.WriteKey[w, "Diffuse", IO.real[mp.kd]];
G3dIO.WriteKey[w, "Specular", IO.real[mp.ks]];
G3dIO.WriteKey[w, "Shininess", IO.real[mp.ke]];
G3dIO.WriteKey[w, "Metallicity", IO.real[mp.km]];
G3dIO.WriteKey[w, "Transmittance", IO.real[mp.kt]];
G3dIO.WriteKey[w, "Transmittance",
IO.real[m.color.rgb.G], IO.real[m.color.rgb.R], IO.real[m.color.rgb.B]];
G3dIO.WriteKey[w, "SaveMaterial", IO.rope[m.name]];
TiogaAccess.Nest[w, -1];
ENDLOOP;
TiogaAccess.Nest[w, -1];
};
ReadMaterials: PROC [lt: LightingTool, merge: BOOLEAN] ~ {
origNum: INT ¬ IF lt.materials # NIL THEN lt.materials.length ELSE 0;
name: ROPE ¬ GetFileName[lt, "Input material file"];
in: STREAM ¬ FS.StreamOpen[name];
IF in = NIL THEN {
Blink[IO.PutFR1["Can't open file %g", IO.rope[name]]];
RETURN;
};
MaterialsFromStream[lt, in, merge];
IF origNum=0 AND lt.materials#NIL AND lt.materials.length>0 THEN DrawMaterialControls[lt];
UpdateMaterialColorSliders[lt];
UpdateMaterialSliders[lt];
};
Blink: PROC [message: ROPE] ~ {
MessageWindow.Append[Rope.Concat["\t\t", message], TRUE];
MessageWindow.Blink[];
};
MaterialsFromStream: PROC [lt: LightingTool, in: STREAM, merge: BOOLEAN] ~ {
IF NOT merge THEN lt.materials ¬ NIL;
{
material: Material ¬ [];
cmdA, rA, gA, bA, vA: Args.Arg;
DO
ENABLE
IO.EndOfStream => EXIT;
Re: PROC [r: ROPE] RETURNS [BOOL] ~ { RETURN[Rope.Equal[firstWord, r, FALSE]]; };
GetOneReal: PROC RETURNS [REAL] ~ {
cmdA, vA: Args.Arg;
[cmdA, vA] ¬ Args.ArgsGetFromRope[line, "%sr"];
RETURN[vA.real];
};
line: ROPE ¬ IO.GetLineRope[in];
firstLetter: INT ¬ Rope.SkipOver[line, 0, " \t"];
firstWord: ROPE ¬ Rope.Substr[line, firstLetter, Rope.SkipTo[line, firstLetter, " \t"]-firstLetter];
SELECT TRUE FROM
Re["Diffuse"] => material.materialProps.kd ¬ GetOneReal[];
Re["Specular"] => material.materialProps.ks ¬ GetOneReal[];
Re["Shininess"] => material.materialProps.ke ¬ GetOneReal[];
Re["Metallicity"] => material.materialProps.km ¬ GetOneReal[];
Re["Transmittance"] => material.materialProps.kt ¬ GetOneReal[];
Re["Color"] => {
[cmdA, rA, gA, bA] ¬ Args.ArgsGetFromRope[line, "%srrr"];
material.color.rgb ¬ [rA.real, gA.real, bA.real];
};
Re["SaveMaterial"] => {
[cmdA, vA] ¬ Args.ArgsGetFromRope[line, "%ss"];
material.name ¬ vA.rope;
lt.materials ¬ AddToMaterialSequence[lt.materials, material];
};
ENDCASE;
ENDLOOP;
};
};
Light Sequence Procedures
AddToLightSequence: PROC [ls: LightSequence, light: Light]
RETURNS [LightSequence]
~ {
light.accelerators ¬ NEW[LightAccelerators];
light.accelerators.cached ¬ NEW[RealArray];
light.accelerators.cacheD ¬ NEW[ColorArray];
light.accelerators.cachep ¬ NEW[RealArray];
light.accelerators.cacheS ¬ NEW[ColorArray];
IF ls = NIL THEN ls ¬ NEW[LightSequenceRep[1]];
IF ls.length = ls.maxLength THEN ls ¬ LengthenLightSequence[ls];
light.direction ¬ G3dVector.Unit[light.direction];
ls[ls.length] ¬ light;
ls.length ¬ ls.length+1;
RETURN[ls];
};
LengthenLightSequence: PROC [ls: LightSequence, amount: REAL ¬ 1.3]
RETURNS [new: LightSequence] ~ {
newLength: NAT ¬ MAX[Real.Ceiling[amount*ls.maxLength], 3];
new ¬ NEW[LightSequenceRep[newLength]];
FOR i: NAT IN [0..ls.length) DO new[i] ¬ ls[i]; ENDLOOP;
new.length ¬ ls.length;
};
RemoveFromLightSequence: PROC [lnum: INT, ls: LightSequence] ~ {
FOR nn: INT IN [lnum..ls.length-1) DO
ls[nn] ¬ ls[nn+1];
ENDLOOP;
ls.length ¬ ls.length-1;
};
Material Sequence Procedures
AddToMaterialSequence: PROC [ms: MaterialSequence, material: Material]
RETURNS [MaterialSequence]
~ {
material.accelerators ¬ NEW [MaterialAccelerators];
IF ms = NIL THEN ms ¬ NEW[MaterialSequenceRep[1]];
IF ms.length = ms.maxLength THEN ms ¬ LengthenMaterialSequence[ms];
ms[ms.length] ¬ material;
ms.length ¬ ms.length+1;
RETURN[ms];
};
LengthenMaterialSequence: PROC [ms: MaterialSequence, amount: REAL ¬ 1.3]
RETURNS [new: MaterialSequence] ~ {
newLength: NAT ¬ MAX[Real.Ceiling[amount*ms.maxLength], 3];
new ¬ NEW[MaterialSequenceRep[newLength]];
FOR i: NAT IN [0..ms.length) DO new[i] ¬ ms[i]; ENDLOOP;
new.length ¬ ms.length;
};
RemoveFromMaterialSequence: PROC [lnum: INT, ms: MaterialSequence] ~ {
FOR nn: INT IN [lnum..ms.length-1) DO
ms[nn] ¬ ms[nn+1];
ENDLOOP;
ms.length ¬ ms.length-1;
};
Utility
Confirm: PROC [op: ROPE] RETURNS [b: BOOL] ~ {
choice: INT ¬ Controls.PopUpRequest[[Rope.Concat["Really ", Rope.Concat[op, "?"]]], LIST[
-- 1 -- [Rope.Concat["Yes, ", op], IO.PutFR1["Confirm %g", IO.rope[op]]],
-- 2 -- [Rope.Concat["No, don't ", op], IO.PutFR1["Cancel %g", IO.rope[op]]]
]];
RETURN[choice=1];
};
-- pass in suffix which will be appended (after a dot) if the filename has no dot
AppendSuffixIfNeeded: PROC [file, suffix: ROPE] RETURNS [ROPE] ~ {
lastDot: INT ← Rope.FindBackward[s1: file, s2: ".", case: FALSE];
IF lastDot > 0 THEN {
nextArrow: INT ← Rope.Find[s1: file, s2: ">", pos1: lastDot, case: FALSE];
nextSlash: INT ← Rope.Find[s1: file, s2: "/", pos1: lastDot, case: FALSE];
IF nextArrow < 0 AND nextSlash < 0 THEN RETURN [file];
};
RETURN[Rope.Cat[file, Rope.Cat[".", suffix]]];
};
Intialize
InitializeMaterials: PROC [lt: LightingTool] ~ {
lt.materials ¬ AddToMaterialSequence[lt.materials,
["Default", [1.0, 1.0, 1.0, 20.0, 1.0, 1.0, 1.0], [[1., 1., 1.]]]];
};
InitializeLights: PROC [lt: LightingTool] ~ {
lt.lights ¬ AddToLightSequence[lt.lights, ["Default", [1.0, 1.0, 1.0], 1.0, [[1., 1., 0.]]]];
lt.lights ¬ AddToLightSequence[lt.lights, ["Ambient", [1.0, 1.0, 1.0], 1.0, [[.1, .1, .1]]]];
};
Start Code
defaultSphereFile: ROPE ¬ FileNames.StripVersionNumber[FS.FileInfo["3dNormalSphere.ais"].fullFName];
usage: ROPE ~ "lighting - interactive light design\nlighting [AIS file name]";
lightingIcon: Icons.IconFlavor ¬ Icons.NewIconFromFile["G3dLighting.icons", 0];
G3dTool.Register["Lighting", LightingCmd, usage];
END.